Structured data for your headless WordPress.
Define custom data structures and manage rows of data exposed through the REST API. A small, standalone alternative to ACF options pages and repeaters.
Features
Everything you need to model content outside of posts and serve it to a front end.
Custom structures
Define named sets of fields with a visual builder. Keys are generated from labels, e.g. "Hero title" becomes hero_title.
Rows editor
Add rows one at a time, reorder them up or down and delete them. Each structure is simply a list of rows.
Public REST API
Read-only endpoints under ldcdt/v1 return clean JSON ready for any headless front end.
Own database tables
Data lives in dedicated tables instead of post meta. A small, standalone alternative to ACF options pages and repeaters.
17 field types
From text and WYSIWYG to images, links, dates, colors, post objects, relationships and repeaters.
Safe type changes
Changing a field type or its settings keeps saved data where possible, e.g. a select value becomes a checkbox list.
Installation
Requires WordPress 6.2+ and PHP 7.4+.
-
1
Copy the
lukdev-custom-data-tablesdirectory towp-content/plugins/(web/app/plugins/on Bedrock). -
2
Activate LUKDEV Custom Data Tables in Plugins. Two database tables are created on activation.
| Table | Columns |
|---|---|
| {prefix}ldcdt_structures | id, name, slug (unique), fields (JSON field definitions), created_at |
| {prefix}ldcdt_rows | id, structure_id, data (JSON values keyed by field key), sort_order |
Usage
Go to LUKDEV Custom Data Tables in the admin menu (requires manage_options).
-
1
Create a structure
Give it a name and optionally a slug, then add fields. Keys are generated from labels and must be unique within a structure.
-
2
Add rows
After saving the structure, the rows editor opens. Rows are saved one at a time and can be moved up, down or deleted.
-
3
Fetch the data
Read your structures from the public, read-only REST API in any front end.
Heads up: deleting a structure also deletes all its rows.
Field types
17 built-in types. Values are sanitized on save and formatted for the API on read.
| Type | Settings | Stored value | API value |
|---|---|---|---|
| text | none | string | string |
| textarea | none | string | string |
| wysiwyg | none | HTML (wp_kses_post) | string |
| number | none | numeric string | string |
| none | string | string | |
| url | none | string | string |
| boolean | none | "1" / "0" | true / false |
| select | options, default | option value | string |
| radio | options, default | option value | string |
| checkbox | options, defaults | list of option values | array of strings |
| image | none | attachment ID | image object or null |
| color_picker | default, opacity, return format | #rrggbb or rgba(r,g,b,a) | string or { red, green, blue, alpha } |
| date_picker | display format, return format, first day | Ymd | date in the return format |
| post_object | post types, multiple | post ID or list of IDs | post object, null or array of posts |
| relationship | post types, filters, max | list of post IDs | array of posts |
| link | none | { page, url, title, target } | { title, url, target } or null |
| repeater | sub-fields | list of items | array of objects |
Repeaters
Repeaters can't be nested. A repeater added as a sub-field is saved as text.
Post object & relationship
Accept any public post type (except attachments) when none are selected. The API returns published posts only.
Links
Point to a page or a custom URL. The page wins if both are set, and its title is used when the link text is empty.
Date picker
Uses PHP date formats. The default for both display and return is d/m/Y.
REST API
Both endpoints are public (no authentication) and read-only.
/wp-json/ldcdt/v1/data
Lists all structures.
[
{
"name": "Team",
"slug": "team",
"fields": [
{ "key": "name", "label": "Name", "type": "text" },
{ "key": "photo", "label": "Photo", "type": "image" }
],
"url": "https://example.com/wp-json/ldcdt/v1/data/team"
}
]Extending
Add a field type
Register it in ldcdt_field_types(), handle settings in
ldcdt_sanitize_fields(), values in
ldcdt_sanitize_value() and
ldcdt_format_value(), and inputs in the admin partials and
admin/js/admin.js.
Hooks & actions
Everything uses the ldcdt prefix: functions (ldcdt_), constants (LDCDT_), tables, options and the REST namespace. Sites upgraded from Custom Headless Data (old chd / chdata prefixes) have their tables and option renamed automatically. Admin form actions:
Ready to go headless?
Install the plugin, define a structure and fetch your data in minutes.
Download v0.1.0