LUKDEV Custom Data Tables

LUKDEV Custom Data Tables

By lukdev
Details
View on WordPress

A WordPress plugin for defining custom data structures (sets of fields) and managing rows of data for them in the admin. The data is exposed through a public, read-only REST API, so a headless front end can use it.

Think of it as a small, standalone alternative to ACF options pages and repeaters. Each structure is a list of rows, stored in its own database tables instead of post meta.

Author: LUKDEV. Text domain: lukdev-custom-data-tables.

Requirements

  • WordPress 6.2+ (queries use the %i identifier placeholder of $wpdb->prepare())
  • PHP 7.4+

Usage

Go to LUKDEV Custom Data Tables in the admin menu. You need the manage_options capability.

  1. Create a structure. Give it a name and, optionally, a slug (the name is used when the slug is empty). Then add fields. A field’s key is generated from its label, for example Hero title becomes hero_title. Keys must be unique within a structure.
  2. Add rows. After you save the structure, the rows editor opens. Rows are saved one at a time, can be moved up or down, and can be deleted.
  3. Fetch the data from the REST API (see below).

Deleting a structure also deletes all its rows.

Field types

Each type is listed with its settings, the value stored in the database and the value returned by the API.

  • text: no settings. Stored: string. API: string.
  • textarea: no settings. Stored: string. API: string.
  • wysiwyg: no settings. Stored: HTML (wp_kses_post). API: string.
  • number: no settings. Stored: numeric string. API: string.
  • email: no settings. Stored: string. API: string.
  • url: no settings. Stored: string. API: string.
  • boolean: no settings. Stored: "1" / "0". API: true / false.
  • select: settings: options, default. Stored: option value. API: string.
  • radio: settings: options, default. Stored: option value. API: string.
  • checkbox: settings: options, defaults. Stored: list of option values. API: array of strings.
  • image: no settings. Stored: attachment ID. API: image object or null.
  • color_picker: settings: default, opacity, return format (string / array). Stored: #rrggbb or rgba(r,g,b,a). API: string, or { red, green, blue, alpha }.
  • date_picker: settings: display format, return format, first day of week. Stored: Ymd. API: date in the return format.
  • post_object: settings: post types, multiple. Stored: post ID or list of IDs. API: post object, null, or array of posts.
  • relationship: settings: post types, filters (search / post type / taxonomy), max. Stored: list of post IDs. API: array of posts.
  • link: no settings. Stored: { page, url, title, target }. API: { title, url, target } or null.
  • repeater: settings: sub-fields. Stored: list of items. API: array of objects.

Notes:

  • Options: an option without a value uses its label as the value, and the other way round.
  • Repeaters can’t be nested. A repeater added as a sub-field is saved as text.
  • Post object and relationship fields accept any public post type (except attachments) when no post types are selected. The API returns published posts only. Drafts and private posts picked in the admin are left out.
  • Link fields point to either a page or a custom URL. The page wins if both are set. The page’s title is used when the link text is empty.
  • Date picker fields use PHP date formats. The default for both display and return is d/m/Y.
  • Changing a field’s type or settings keeps saved data where possible. For example, a single post object becomes a list when multiple is turned on, and a select value becomes a one-item checkbox list. Fields saved before field types existed are treated as textarea.

REST API

Both endpoints are public (no authentication) and read-only.

GET /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"
    }
]

GET /wp-json/ldcdt/v1/data/{slug}

Returns one structure with its rows, in the order set in the admin. Each item has an id plus one property per field key. Returns 404 (ldcdt_not_found) for an unknown slug.

{
  "name": "Team",
  "slug": "team",
  "fields": [ ... ],
  "items": [
    {
      "id": 1,
      "name": "Jane Doe",
      "photo": {
        "id": 42,
        "url": "https://example.com/app/uploads/jane.jpg",
        "alt": "",
        "width": 1200,
        "height": 800,
        "sizes": {
          "thumbnail": { "url": "...", "width": 150, "height": 150 }
        }
      }
    }
  ]
}

Shapes of the other object values:

  • Post (post_object, relationship): { id, type, slug, title, url, date }, where date is an ISO 8601 GMT date.
  • Link: { title, url, target }, where target is "" or "_blank".

Project structure

lukdev-custom-data-tables.php           Plugin header, constants, bootstrap
includes/
  install.php                  Table names, creation and upgrades
  fields.php                   Field types, sanitization and API formatting
  data.php                     Reading structures and rows
  rest-api.php                 REST routes (ldcdt/v1)
admin/
  admin.php                    Menu, assets, page router, notices
  handlers.php                 admin-post.php form handlers, post search AJAX
  partials/                    List, structure builder and rows editor screens
  css/, js/                    Admin assets
  vendor/select2/              Select2 4.1.0 (MIT)<h3>Extending</h3>

– To add a field type, register it in ldcdt_field_types(). Then handle its settings in ldcdt_sanitize_fields(), its value in ldcdt_sanitize_value() and ldcdt_format_value(), and its inputs in the admin partials and admin/js/admin.js.
– Everything uses the ldcdt prefix: functions (ldcdt_), constants (LDCDT_), tables, options, the REST namespace, CSS classes and JS. Sites upgraded from Custom Headless Data (old chd / chdata prefixes) have their tables and option renamed automatically. Admin form actions are ldcdt_save_structure, ldcdt_delete_structure, ldcdt_save_row, ldcdt_delete_row and ldcdt_move_row. The post search AJAX action is ldcdt_search_posts.

Details

Plugin code:
lukdev-custom-data-tables
Plugin version:
0.1.0
Author:
Outdated:
No
WP version:
6.2 or higher
PHP version:
7.4 or higher
Test up to WP version:
7.1.3
Total installations:
0
Last updated:
2026-10-08
Rating:
Times rated:
0
custom-fields
data
headless
repeater
rest-api