Documentation
GriD12
A configuration-driven PHP CRUD grid framework for MySQL/MariaDB. Describe a table once, include one file, and get a secure, responsive data grid with search, sorting, pagination, lookups, and — on the Pro edition — full Create/Update/Delete, CSV/PDF export, conditional formatting, grouped summary reports, and more.
Overview
GriD12 has one job: turn a small PHP configuration array into a complete CRUD screen. There is no build step, no admin UI to configure, and no database of its own to install — you point it at your MySQL/MariaDB table and it renders the grid directly from your schema.
$config = [
'table' => 'payments',
'primary_key' => 'aid',
'title' => 'Supplier Payments',
'permissions' => 'CRUD',
'columns' => [ /* ... */ ],
];
include __DIR__ . '/grid12/grid12.php';
That snippet — one config array and one include — is the entire workflow. Everything else (rendering, validation, security, formatting) is handled by the framework.
Design principles
- Configuration over boilerplate. Describe what you want; the framework does the rest.
- Secure by default. Prepared statements, schema whitelisting, CSRF protection and output escaping are always on — not opt-in.
- Open and readable. No encryption, no obfuscation. The code is yours to read and adapt.
- Localized from day one. Six bundled languages with locale-aware number and date formatting.
Requirements
| PHP | 8.1 or later, with the pdo_mysql, dom, and mbstring extensions enabled |
|---|---|
| Database | MySQL or MariaDB (the connection layer talks to these via PDO) |
| Web server | Apache, Nginx, or PHP's built-in development server — any standard PHP host works |
| Front end | None to install — Bootstrap 5, Select2, flatpickr, and Jodit GriD12 Pro are loaded from CDN automatically |
| PDF export (optional) | The Dompdf library GriD12 Pro — server-side PHP, not CDN-loaded, so it must be installed manually. See PDF Export. |
Installation
- Copy the
grid12/folder onto your server, as its own standalone directory. - Create your database and import the demo schema (or your own tables):
CREATE DATABASE grid12_demo CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;mysql -u root -p grid12_demo < demo/schema_and_seed.sql - Edit
grid12/config.php: set your database credentials, the global date format, UI language, and visual theme. - Create your own form file anywhere in your application and
includethe framework (see Quick Start).
Free vs. Pro packages
The Free package ships without create.php, update.php, delete.php and summary.php — it is read-only by design. Upgrading to Pro is just copying those four files into the same grid12/ folder; nothing in config.php needs to change. The edition is detected automatically from which files are present on disk.
Quick Start
Create a file in your own application directory (not inside grid12/ itself):
<?php
// my-app/suppliers.php
$config = [
'table' => 'suppliers',
'primary_key' => 'aid',
'title' => 'Suppliers',
'permissions' => 'CRUD',
'search_enabled' => true,
'export_csv' => true,
'default_sort' => ['field' => 'descr_supplier', 'dir' => 'ASC'],
'columns' => [
['field' => 'id_supplier', 'label' => 'Code', 'validation' => ['required' => true]],
['field' => 'descr_supplier', 'label' => 'Company Name', 'searchable' => true],
['field' => 'city', 'label' => 'City'],
],
];
include __DIR__ . '/grid12/grid12.php';
Open that file in a browser. You immediately get a paginated, searchable, sortable grid with Create/Edit/Delete and CSV export — driven entirely by the array above.
Folder Structure
grid12/
├── config.php # global connection + defaults (edit this)
├── grid12.php # entry point you include from your own files
├── read.php # list / search / sort / pagination (always present)
├── create.php # ─┐
├── update.php # │ Pro only — auto-detected by file presence
├── delete.php # │
├── summary.php # ─┘
├── .htaccess # denies direct web access to internals
├── core/
│ ├── Db.php # PDO wrapper + schema whitelisting
│ ├── Grid.php # the engine: queries, validation, formatting
│ ├── helpers.php # escaping, formatting, sanitization, theming
│ └── view.php # shared HTML chrome + form field rendering
├── lang/
│ └── en.php el.php de.php es.php fr.php it.php
└── vendor/dompdf/ # optional, NOT bundled — install for PDF export
└── vendor/autoload.php # see "PDF Export" for install steps
Global Configuration config.php
These are the framework-wide defaults. Most can be overridden per form (see below).
| Key | Type | Default | Description |
|---|---|---|---|
db | array | — | host, port, name, user, pass, charset for the PDO connection |
date_format | string | d-m-Y | Token pattern: Y y m n d j M F — e.g. d-M-Y → 31-Dec-2025 |
lang | string | en | One of en el de es fr it; overridable per form |
theme | string | light | light dark navy gray; overridable per form |
create.php/update.php/delete.php/summary.php exist on disk.
Form Configuration
Keys accepted in the $config array of an individual form file.
| Key | Type | Description |
|---|---|---|
table | string | The MySQL table to manage (whitelisted against the live schema) |
primary_key | string | Default aid; hidden from the form, used for edit/delete |
title | string | Page heading |
title_align | string | left (default) | center | right |
lang | string | Per-form override of the global UI language |
theme | string | Per-form override of the global visual theme |
font | string | Per-form override of the global Google Fonts family, e.g. 'Ubuntu'. Double-check the replacement font covers this form's lang characters (see Localization) |
permissions | string | Any combination of C R U D; the Free edition only ever effectively grants R |
search_enabled | bool | Show the per-column search row |
view_enabled | bool | Default true. Shows a read-only "View" detail page for a single record. Available in both Free and Pro — set to false to hide the button and block the detail page |
export_csv | bool | Offer CSV export GriD12 Pro |
export_pdf | bool | Offer PDF export GriD12 Pro — see PDF Export. Independent of export_csv; a form may offer one, both, or neither |
default_sort | array | ['field' => ..., 'dir' => 'ASC'|'DESC'] |
page_size_options | array | e.g. [10, 20, 30, 40, 50, 100] |
default_page_size | int | Initial page size |
base_where | string | A permanent, developer-written SQL condition ANDed into every query on this form (grid, CSV, edit/delete lookup). Never build this from request data. |
summary | array | GriD12 Pro grouped-summary report — see Summary Reports |
columns | array | Ordered list of column definitions — see next section |
Column Options Reference
Each entry in $config['columns'] accepts:
| Key | Default | Description |
|---|---|---|
field | — | Database column name (whitelisted) |
label | field | Display name |
type | text | See Column Types |
header_align / data_align | left | left | center | right |
number_format | — | e.g. 9.999.999,99 or 9,999,999.99 — display only; the raw decimal is stored |
date_format | global | Per-column override of the date display format |
searchable | false | Show this column in the per-column search row |
hidden | false | Excluded from the grid, CSV export, summary report and delete confirmation — still shown and editable in create/update forms |
read_only | false | Shown everywhere, but the form input is disabled and the value is never written — enforced server-side, not just in the browser. (readonly, no underscore, also works.) |
default | — | Default value for new records |
validation | [] | required, max, min, max_val, regex (plus email via type) |
lookup | — | ['table', 'value', 'display', 'order_by'] — see select type |
compute | — | ['operands' => [...], 'operation' => 'add'|'subtract'|'multiply'|'divide'] — see Computed Columns |
image_width / image_height | — | Pixels, for type => 'image' — see Image Columns |
conditional_format | — | GriD12 Pro Ordered list of highlight rules — see Conditional Formatting |
Column Types
text / textarea / email
Plain or multi-line text input; email additionally validates the format.
['field' => 'comments', 'label' => 'Comments', 'type' => 'textarea', 'validation' => ['max' => 255]]
number
['field' => 'amount', 'label' => 'Amount', 'type' => 'number',
'number_format' => '9,999.99', 'validation' => ['required' => true, 'min' => 0]]
date
Rendered with a localized date picker (flatpickr) that follows the form's own lang, not the browser's locale.
['field' => 'payment_date', 'label' => 'Payment Date', 'type' => 'date', 'date_format' => 'd-M-Y']
checkbox
['field' => 'is_active', 'label' => 'Active', 'type' => 'checkbox']
select (lookup)
Stores one column's value, displays another table's column — a searchable dropdown (Select2) in forms.
[
'field' => 'id_supplier', 'label' => 'Supplier', 'type' => 'select',
'lookup' => [
'table' => 'suppliers', 'value' => 'id_supplier',
'display' => 'descr_supplier', 'order_by' => 'descr_supplier ASC',
],
]
html GriD12 Pro
Rich text, edited with the Jodit WYSIWYG editor. The HTML is sanitized against an allowlist server-side before storage; the grid shows a plain-text preview.
['field' => 'body', 'label' => 'Content', 'type' => 'html', 'validation' => ['required' => true]]
computed
A virtual column with no database field — see Computed Columns.
image
A filename or URL, rendered as an <img> — see Image Columns.
Permissions & Editions
permissions is any combination of the letters C R U D. The effective permission is the intersection of what you configure and what the edition allows:
| Edition | Allows | Detected by |
|---|---|---|
| Free | Read only — search, sort, pagination, lookups, formatting, the View detail page. No Create/Update/Delete, no CSV/PDF export, no summary, no conditional formatting. | create.php/update.php/delete.php/summary.php absent |
| Pro | Everything — adds Create, Update, Delete, CSV export, PDF export (requires Dompdf, see PDF Export), the grouped summary report, conditional formatting, and rich text (html columns). | Those four files present in the same folder |
This is enforced server-side in every operation file — hiding a button is not the security boundary, the permission check inside the PHP code is.
Security
- Prepared statements everywhere. Every value goes through PDO bound parameters — never string-concatenated into SQL.
- Schema whitelisting. Table and column names are validated against
information_schemaat startup; anything unrecognized throws immediately. - CSRF tokens on every Create/Update/Delete form, verified server-side.
- Output escaping. All display values are HTML-escaped by default.
- Sanitized rich text.
html-type columns are passed through an allowlist sanitizer (tags, attributes, CSS, URL schemes) before storage.
base_where is plain PHP source you write — it is never built from $_GET/$_POST/cookies or any other request data. A few obviously dangerous patterns (;, SQL comments, UNION/DROP/DELETE) are rejected at startup as a sanity check, but this is defense-in-depth, not a sandbox for untrusted input.
Theming
Four built-in themes, set globally or per form via 'theme' => '...':
light | Default — white background, dark text |
|---|---|
dark | Bootstrap's native dark color mode — near-black background |
navy | Dark mode re-pointed to a dark blue palette |
gray | Light mode re-pointed to a light gray palette |
Implemented via Bootstrap 5.3's color-mode CSS variables, so tables, forms, buttons, alerts and pagination all keep correct contrast automatically — including in the bundled Select2, flatpickr and Jodit widgets, which are re-themed to match.
Localization
Six bundled UI languages: en English, el Greek, de German, es Spanish, fr French, it Italian — covering buttons, messages, validation text, month names, and the date picker / rich-text editor toolbars.
'lang' => 'el', // overrides the global default for this form only
Add another language by dropping a new lang/<code>.php file with the same keys as lang/en.php.
Rich Text Editing GriD12 Pro
type => 'html' columns are edited with the Jodit WYSIWYG editor, localized to the form's language. Content is sanitized server-side against an allowlist (tags, attributes, inline CSS, URL schemes) before it is ever stored — independent of whatever the editor itself produces.
['field' => 'body', 'label' => 'Content', 'type' => 'html']
Summary Reports GriD12 Pro
Group the grid's own rows by one column and show a per-group subtotal (and grand total) of another, with collapsible sections — without leaving the full row detail behind.
'summary' => [
'group_by' => 'id_supplier',
'column' => 'amount',
'function' => 'SUM', // or AVG | COUNT | MIN | MAX
],
The report respects base_where, resolves lookups to their display names, reuses the column's own number_format, and supports CSV export of the grouped result.
Computed Columns
A virtual column with no database field of its own, calculated from two or more numeric columns:
[
'field' => 'line_total', 'label' => 'Line Total', 'type' => 'computed',
'number_format' => '9,999.99',
'compute' => [
'operands' => ['unit_price', 'quantity'],
'operation' => 'multiply', // add | subtract | multiply | divide (+ - * / also accepted)
],
]
- Never written to the database and never shown in create/update forms — there is nothing to store or type in.
- Fully visible in the grid, CSV export and delete confirmation, formatted like any number column.
- Operands are always fetched even if not independently listed as visible columns.
- Division by zero shows as blank instead of crashing.
Image Columns
Not a file-upload field — a plain text input where you type either a filename or a URL:
[
'field' => 'logoimg', 'label' => 'Logo', 'type' => 'image',
'image_width' => 80, 'image_height' => 80, // optional; omit either to scale proportionally
]
- Bare filename (e.g.
supplier_logo.jpg) — resolved by the browser relative to the form's own directory, exactly like any normal relative<img src>. - Full URL (e.g.
https://example.com/logo.png) — used as-is. - The value is checked against the same URL allowlist used for rich-text images (
javascript:and similar schemes are rejected). - CSV export shows the plain filename/URL text, not the rendered image.
- PDF export embeds the picture itself for a bare filename (read straight off disk, no network fetch) — see PDF Export. A full URL is still shown as plain text there, same as CSV.
Conditional Formatting GriD12 Pro
Highlight a cell in the grid when its value matches a rule — an ordered list on the column's conditional_format key. Rules are evaluated in order; the first match wins, like a normal CSS rule list cascading.
[
'field' => 'amount', 'label' => 'Amount', 'type' => 'number',
'number_format' => '9.999.999,99',
'conditional_format' => [
[
'op' => 'gt', // eq | ne | lt | le | gt | ge
'value' => 80000,
'style' => ['background-color' => '#0f5132', 'color' => '#ffffff'],
],
],
]
- Operators:
eqneltlegtge. Both sides are compared as numbers when the value looks numeric, as strings otherwise. - Style keys are allowlisted:
background-color,background,color,font-weight,border,border-color. Anything else (and dangerous values likeexpression(),url(),javascript:) is silently dropped rather than breaking the grid. - Setting a background color also sets Bootstrap 5's
--bs-table-bg-statevariable, so the color stays exact on striped/hover rows instead of being tinted by the table's own zebra effect. - Free edition: the key is accepted but silently ignored — no error, the grid just renders unstyled, exactly like
export_csv/summarydegrade there.
See payments_form.php for a live example (payments over 80,000 highlighted).
CSV Export GriD12 Pro
Enabled with 'export_csv' => true. Exports respect the active search filters and sort order, use UTF-8 with a BOM and a semicolon delimiter so non-English text opens correctly in Excel, and apply the same display formatting as the grid.
PDF Export GriD12 Pro
Enabled per form with 'export_pdf' => true. Like CSV export, it exports the full filtered/sorted result set (not just the current page), applying the same display formatting as the grid — A4 landscape, one table, DejaVu Sans (covers every bundled language, including Greek, with no extra font install).
'export_pdf' => true, // shown next to "Export CSV" once Dompdf is installed (see below)
- Image columns (
type => 'image'): a bare filename is embedded as the actual picture, read directly off disk and inlined as base64 — no network request is ever made for it. A fullhttp(s)URL is shown as plain text instead, to keep PDF generation fast and avoid enabling Dompdf's remote-fetch settings entirely. - Respects
base_whereand the active search filters/sort, same as CSV export. - Independent of
export_csv— enable either, both, or neither per form.
Installing Dompdf (required, not bundled)
PDF export uses the Dompdf library. Unlike the CDN-loaded front-end assets (Bootstrap, Select2, flatpickr, Jodit), Dompdf is server-side PHP code — there is no <script src> equivalent for it, so it must already exist on disk before a page request can use it.
'export_pdf' => true set — and everything else in GriD12 (CSV export included) keeps working exactly as before. See FAQ if the button is missing after installing it.
Install it once, under the grid12/ folder — either method produces the same result:
Option A — Composer (recommended)
cd grid12
mkdir -p vendor/dompdf
cd vendor/dompdf
composer require dompdf/dompdf
This creates grid12/vendor/dompdf/vendor/autoload.php — the path config.php expects by default.
Option B — Manual download (no Composer available)
- Download the packaged release zip from the Dompdf releases page — the asset literally named
dompdf_X-Y-Z.zip. Not GitHub's auto-generated "Source code" zip, which is missing dependencies. - Extract it so its contents land under
grid12/vendor/dompdf/vendor/, i.e.grid12/vendor/dompdf/vendor/autoload.phpexists.
Verify and configure
Confirm the file exists at the path config.php's dompdf_autoload key expects (relative to grid12/):
// grid12/config.php
'dompdf_autoload' => 'vendor/dompdf/vendor/autoload.php', // default; change only if you installed elsewhere
No other configuration is needed. Once the file is present, canExportPdf() picks it up automatically (a cheap is_file() check on every page load — Dompdf itself is only actually loaded when a PDF is requested) and the "Export PDF" button appears on every form with 'export_pdf' => true.
Examples
The examples/ folder ships ready-to-run forms demonstrating every feature on this page — lookups, theming, grouping, hidden/read-only columns, computed columns, image columns, and rich text. Open examples/index.php for a guided index of all of them.
FAQ & Troubleshooting
"grid12: unknown table/column" on startup
The schema whitelist couldn't find that table or column in the connected database. Check config.php's db credentials and confirm the name is spelled exactly as it appears in information_schema.
The date picker or rich-text toolbar is in the wrong language
Set 'lang' explicitly on that form — it overrides the global default in config.php for every widget on the page, including flatpickr and Jodit.
My Pro features disappeared after copying the folder
The edition is detected purely from file presence. If create.php, update.php, delete.php or summary.php were excluded during copy, those specific capabilities silently fall back to read-only.
An image column shows a broken image icon
For a bare filename, the file must sit in the same directory as the form file that includes grid12.php. For a URL, confirm it is reachable and uses http/https.
The "Export PDF" button doesn't appear
Three things are required together: the paid edition, 'export_pdf' => true on that form, and the Dompdf library actually installed on disk. The button is hidden (not an error) whenever any one of those is missing — see PDF Export for install steps and the exact path config.php expects.
Licensing & Support
Every paid license includes 12 months of updates and support. Renewal afterward is optional — your installed version keeps working indefinitely. See the pricing page on the main site for current plans, or the project's README.md for the full column/feature reference in plain text.
GriD12 — configuration-driven PHP CRUD grids.