Title: LUKDEV Custom Data Tables
Author: lukdev
Published: <strong>October 8, 2026</strong>
Last modified: October 8, 2026

---

Search plugins

![](https://ps.w.org/lukdev-custom-data-tables/assets/banner-772x250.png?rev=3734310)

![](https://ps.w.org/lukdev-custom-data-tables/assets/icon.svg?rev=3734310)

# LUKDEV Custom Data Tables

 By [lukdev](https://profiles.wordpress.org/lukdev/)

[Download](https://downloads.wordpress.org/plugin/lukdev-custom-data-tables.0.1.0.zip)

 * [Details](https://wordpress.org/plugins/lukdev-custom-data-tables/#description)
 * [Reviews](https://wordpress.org/plugins/lukdev-custom-data-tables/#reviews)
 *  [Installation](https://wordpress.org/plugins/lukdev-custom-data-tables/#installation)
 * [Development](https://wordpress.org/plugins/lukdev-custom-data-tables/#developers)

 [Support](https://wordpress.org/support/plugin/lukdev-custom-data-tables/)

## Description

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](https://www.lukaszdabik.pl/). 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`.

## Screenshots

[⌊List of data structures.⌉⌊List of data structures.⌉[

List of data structures.

[⌊Structure builder with field settings.⌉⌊Structure builder with field settings.⌉[

Structure builder with field settings.

[⌊Rows editor.⌉⌊Rows editor.⌉[

Rows editor.

[[

## Installation

 1. Copy the `lukdev-custom-data-tables` directory to `wp-content/plugins/` (`web/app/
    plugins/` on Bedrock).
 2. Activate **LUKDEV Custom Data Tables** in _Plugins_.

On activation the plugin creates two tables. If the plugin is already active and
the stored `ldcdt_db_version` option differs from `LDCDT_DB_VERSION`, it creates
or updates them on `plugins_loaded`.

 * `{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`

## FAQ

### Is the REST API protected?

No. Both endpoints are public and read-only, so don’t store private data in a structure.
Editing data in the admin requires the `manage_options` capability.

### Where is the data stored?

In two custom tables, `{prefix}ldcdt_structures` and `{prefix}ldcdt_rows`, not in
post meta or options.

### Can I nest repeaters?

No. A repeater added as a sub-field of another repeater is saved as a `text` field.

### Why doesn’t a post I picked show up in the API?

The API returns published posts only. Drafts and private posts picked in a post 
object or relationship field are left out.

### What happens to saved data when I change a field’s type?

The plugin keeps the data where possible. For example, a select value becomes a 
one-item checkbox list, and a single post object becomes a list when _multiple_ 
is turned on.

## Reviews

There are no reviews for this plugin.

## Contributors & Developers

“LUKDEV Custom Data Tables” is open source software. The following people have contributed
to this plugin.

Contributors

 *   [ lukdev ](https://profiles.wordpress.org/lukdev/)

[Translate “LUKDEV Custom Data Tables” into your language.](https://translate.wordpress.org/projects/wp-plugins/lukdev-custom-data-tables)

### Interested in development?

[Browse the code](https://plugins.trac.wordpress.org/browser/lukdev-custom-data-tables/),
check out the [SVN repository](https://plugins.svn.wordpress.org/lukdev-custom-data-tables/),
or subscribe to the [development log](https://plugins.trac.wordpress.org/log/lukdev-custom-data-tables/)
by [RSS](https://plugins.trac.wordpress.org/log/lukdev-custom-data-tables/?limit=100&mode=stop_on_copy&format=rss).

## Changelog

### 0.1.0

 * First release.

## Meta

 *  Version **0.1.0**
 *  Last updated **2 days ago**
 *  Active installations **Fewer than 10**
 *  WordPress version ** 6.2 or higher **
 *  Tested up to **7.1.3**
 *  PHP version ** 7.4 or higher **
 * Tags
 * [custom fields](https://wordpress.org/plugins/tags/custom-fields/)[data](https://wordpress.org/plugins/tags/data/)
   [headless](https://wordpress.org/plugins/tags/headless/)[repeater](https://wordpress.org/plugins/tags/repeater/)
   [rest-api](https://wordpress.org/plugins/tags/rest-api/)
 *  [Advanced View](https://wordpress.org/plugins/lukdev-custom-data-tables/advanced/)

## Ratings

No reviews have been submitted yet.

[Your review](https://wordpress.org/support/plugin/lukdev-custom-data-tables/reviews/#new-post)

[See all reviews](https://wordpress.org/support/plugin/lukdev-custom-data-tables/reviews/)

## Contributors

 *   [ lukdev ](https://profiles.wordpress.org/lukdev/)

## Support

Got something to say? Need help?

 [View support forum](https://wordpress.org/support/plugin/lukdev-custom-data-tables/)

## Donate

Would you like to support the advancement of this plugin?

 [ Donate to this plugin ](https://www.lukaszdabik.pl/)