Contents

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

PHP8.1 or later, with the pdo_mysql, dom, and mbstring extensions enabled
DatabaseMySQL or MariaDB (the connection layer talks to these via PDO)
Web serverApache, Nginx, or PHP's built-in development server — any standard PHP host works
Front endNone 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.
Outbound access to the CDN domains (jsDelivr, cdnjs, Google Fonts) is required for the page chrome and widgets to load correctly in the browser.

Installation

  1. Copy the grid12/ folder onto your server, as its own standalone directory.
  2. 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
  3. Edit grid12/config.php: set your database credentials, the global date format, UI language, and visual theme.
  4. Create your own form file anywhere in your application and include the 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).

KeyTypeDefaultDescription
dbarrayhost, port, name, user, pass, charset for the PDO connection
date_formatstringd-m-YToken pattern: Y y m n d j M F — e.g. d-M-Y → 31-Dec-2025
langstringenOne of en el de es fr it; overridable per form
themestringlightlight dark navy gray; overridable per form
Edition is not set here. It is detected automatically from whether create.php/update.php/delete.php/summary.php exist on disk.

Form Configuration

Keys accepted in the $config array of an individual form file.

KeyTypeDescription
tablestringThe MySQL table to manage (whitelisted against the live schema)
primary_keystringDefault aid; hidden from the form, used for edit/delete
titlestringPage heading
title_alignstringleft (default) | center | right
langstringPer-form override of the global UI language
themestringPer-form override of the global visual theme
fontstringPer-form override of the global Google Fonts family, e.g. 'Ubuntu'. Double-check the replacement font covers this form's lang characters (see Localization)
permissionsstringAny combination of C R U D; the Free edition only ever effectively grants R
search_enabledboolShow the per-column search row
view_enabledboolDefault 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_csvboolOffer CSV export GriD12 Pro
export_pdfboolOffer PDF export GriD12 Pro — see PDF Export. Independent of export_csv; a form may offer one, both, or neither
default_sortarray['field' => ..., 'dir' => 'ASC'|'DESC']
page_size_optionsarraye.g. [10, 20, 30, 40, 50, 100]
default_page_sizeintInitial page size
base_wherestringA permanent, developer-written SQL condition ANDed into every query on this form (grid, CSV, edit/delete lookup). Never build this from request data.
summaryarrayGriD12 Pro grouped-summary report — see Summary Reports
columnsarrayOrdered list of column definitions — see next section

Column Options Reference

Each entry in $config['columns'] accepts:

KeyDefaultDescription
fieldDatabase column name (whitelisted)
labelfieldDisplay name
typetextSee Column Types
header_align / data_alignleftleft | center | right
number_formate.g. 9.999.999,99 or 9,999,999.99 — display only; the raw decimal is stored
date_formatglobalPer-column override of the date display format
searchablefalseShow this column in the per-column search row
hiddenfalseExcluded from the grid, CSV export, summary report and delete confirmation — still shown and editable in create/update forms
read_onlyfalseShown 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.)
defaultDefault 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_heightPixels, for type => 'image' — see Image Columns
conditional_formatGriD12 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:

EditionAllowsDetected by
FreeRead 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
ProEverything — 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_schema at 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' => '...':

lightDefault — white background, dark text
darkBootstrap's native dark color mode — near-black background
navyDark mode re-pointed to a dark blue palette
grayLight 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: eq ne lt le gt ge. 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 like expression(), url(), javascript:) is silently dropped rather than breaking the grid.
  • Setting a background color also sets Bootstrap 5's --bs-table-bg-state variable, 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/summary degrade 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 full http(s) URL is shown as plain text instead, to keep PDF generation fast and avoid enabling Dompdf's remote-fetch settings entirely.
  • Respects base_where and 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.

PDF export is entirely optional. If Dompdf isn't installed, the "Export PDF" button simply doesn't appear — even with '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)

  1. 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.
  2. Extract it so its contents land under grid12/vendor/dompdf/vendor/, i.e. grid12/vendor/dompdf/vendor/autoload.php exists.

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.