Quick Start: Your First CRUD Page
A working add/edit/delete grid in three lines of PHP - no HTML, no layout, no wiring.
The whole page
Create pages/payments.php with just this:
$xcrud = Xcrud::get_instance();
$xcrud->table('payments');
$xcrud->route('payments');
Visit /payments and that is a complete CRUD screen - list, search, sort, add, edit, delete. The file never calls render() or prints anything itself: core/router.php notices the page produced no output of its own, so it calls render() for you and wraps the result in the site's normal page shell (or the dashboard shell, if login/roles are enabled). That auto-wrap only happens when the page is silent - the moment a page echoes its own HTML (like a widget-less page or one with several widgets), the router leaves it alone. See Multiple Widgets on One Page for that case.
get_instance()
Xcrud::get_instance() returns a fresh, independent widget instance - despite the name, it is not a true singleton. Every call returns a brand-new object with its own table, columns, fields, and hooks. Multiple widgets can coexist happily on one page, each from its own get_instance() call:
$customers = Xcrud::get_instance();
$customers->table('customers');
$customers->route('customers');
$orders = Xcrud::get_instance();
$orders->table('orders');
$orders->route('orders');
table()
$xcrud->table(string $name) binds the widget to a real database table. The name is checked against your database's own schema (it must exist and have a single-column primary key) and against XcrudConfig::$blacklistedTables - a security blacklist in src/Config.php. Every table is reachable by default; a table only becomes unusable once it is explicitly added to that list. Calling table() on a blacklisted or non-existent table throws immediately. See Security: Blacklisting Tables & Columns for the full model, including column-level blocking.
| Parameter | Type | Default | Description |
|---|---|---|---|
$name | string | - | Real table name in the connected database. Must not be in $blacklistedTables, must exist, and must have a single-column primary key. |
route()
$xcrud->route(string $name) sets the URL slug the widget answers to - it is how core/router.php matches a request like /payments back to this exact widget instance, and it's also the key ajax_crud.php uses to re-resolve the same page's configuration (columns, hooks, buttons) on every AJAX request. Calling table() alone already sets a default route matching the table name, so route() is only required when you want the URL to differ from the table name.
render() vs page()
Once configured, a widget produces its markup one of two ways:
| Method | Returns | When to use it |
|---|---|---|
render() | Just the widget's own HTML (mount point + assets + init script) | Embedding into a page you're already writing yourself - alongside other widgets, custom HTML, or a decoupled frontend |
page() | A complete standalone HTML document (doctype, head, title, body) wrapping render() | A route that is nothing but this one grid - what the router falls back to for a silent page like the one above |
$xcrud = Xcrud::get_instance();
$xcrud->table('payments');
$xcrud->route('payments');
echo $xcrud->page(); // full <html>...</html> document
You rarely need to call either one explicitly for a single-widget page - leaving the page silent (as in the three-line example above) lets the router call render() for you and handle the surrounding shell.