dabadataDocs
Reference / WordPress plugin

Reference

WordPress plugin

The dabadata plugin puts live data on WordPress pages. Connect the site to a project in two clicks, map WordPress roles to the project's roles, and add the Live table block: everyone sees rows change as they happen, and logged-in visitors add and edit them under the project's rules. The plugin also mirrors the site's own content changes into dabadata.

Quick start

  1. Create a dabadata account (free).
  2. Download the plugin. In WordPress: Plugins > Add New > Upload Plugin, choose the zip, Install Now, Activate.
  3. Settings > dabadata > Connect to dabadata. Sign in, choose New project from the WordPress template, press Connect. You are back in WordPress, connected.
  4. Edit a page, add the Live table block, publish. Logged-in editors add and change notes; everyone sees every change live.

Who may do what comes from the project's rules and your role mapping (Roles). The rest of this page explains each step in detail.

Install

Download the plugin zip and upload it in Plugins > Add New > Upload Plugin, then activate it. It needs WordPress 6.5 and PHP 8.1 or newer (with the sodium extension, which PHP ships), and the WordPress server must be able to call https://dabadata.com. The site itself does not have to be public: a local WordPress works.

The plugin is not in the WordPress.org directory yet, so WordPress does not update it by itself: to update, download the new zip and upload it again (WordPress offers to replace the installed version). Your connection and settings stay.

From the repository, plugin/build-zip.sh builds the same zip into plugin/dist/. For local development you can instead symlink plugin/dabadata into wp-content/plugins/, which is what site/setup.sh does for the local test site.

Connect

In Settings > dabadata, check the server address (https://dabadata.com, or your own) and press Connect to dabadata:

  1. The browser opens the console at /console/connect?site=<site>&return=<admin url>&state=<random>. Signed out, you sign in or create an account first and land back on it.
  2. The console asks Connect https://your-site to <project>? Pick a workspace, a project (or New project from the WordPress template) and the environment (live by default). The page shows the host the browser goes back to; when it differs from the site's host you confirm it with a checkbox.
  3. Connect makes a one-time code (POST /v1/connect/codes) and sends the browser back to WordPress with dabadata_code and the same state.
  4. WordPress checks the state, generates a 48-character site secret, and exchanges the code server to server (POST /v1/connect/exchange). The answer carries the keys; the settings page says Connected to dabadata project prj_... (live).

On the server the exchange stores the site secret as the project secret WP_SITE_<hash>, adds the site as an HS256 identity provider (issuer wp_<hash>, a new definition version by wordpress connect) and mints a secret key for the site, listed on the console's Keys page as WordPress <host>. Connecting the same site again replaces its secret and its key.

Disconnect revokes the site's key on the server and removes the keys and the site secret from WordPress.

The WordPress template

A project made from the wordpress template declares the WordPress roles administrator, editor, author, contributor, subscriber and visitor, and one table for the first block:

json
"notes": {
  "columns": { "title": { "type": "text" }, "done": { "type": "bool", "default": false }, "author": { "type": "userId" } },
  "rules": {
    "read": "anyone",
    "create": { "role": ["administrator", "editor", "author"] },
    "update": { "any": [{ "role": ["administrator", "editor"] }, { "owner": "author" }] },
    "delete": { "role": ["administrator", "editor"] }
  }
}

Any other project works too: Connect adds a visitor role when the project has none (the issuer's default role), and the block shows whichever table you name.

Identity bridge

WordPress stays the login. For a logged-in user the plugin signs a short-lived JWT with the site secret, and the page's script hands it to the dabadata client, so the user acts in the project under its rules:

ClaimValue
issThe site's address as the server stored it, e.g. https://example.org
auddabadata
subwp:<user id> - the id the rules see (owner columns hold it)
nameThe display name
roleThe project role mapped from the user's WordPress role
iat, expIssued now, valid 10 minutes

The page gets { url, key, token } and renews the token a minute before it expires through GET /wp-json/dabadata/v1/token, which needs the WordPress login cookie and the REST nonce (X-WP-Nonce); without them it answers 401. Logged-out visitors get no token: the client is anonymous with the publishable key.

Roles

Settings > dabadata > Roles maps every WordPress role, custom roles included, to one of the project's roles (read from the project's definition). By default a WordPress role maps to the project role of the same name, and anything else to visitor. A user with several WordPress roles gets the mapping of the first. A role the project does not declare counts as visitor on the server too, so a mapping can never make anyone more than the project allows. Changes apply to each visitor's next token, within 10 minutes.

Live table block and shortcode

The block Live table (dabadata/live-table) and the shortcode render the same thing:

text
[dabadata_table table="notes" columns="title,done" editable="1" orderby="created_at desc" limit="50" title="Team notes"]
AttributeMeaning
tableA table of the connected environment. Default notes.
columnsComma-separated columns to show. Empty shows every declared column (the block's default is title,done).
editableLogged-in visitors get an add form and Edit / Delete on each row; a bool column becomes a checkbox they can tick. Default on for the block, off for the shortcode.
orderByA column, optionally followed by desc (or prefixed with -). Default: oldest first.
limitAt most this many rows, 1 to 200. Default 50.
titleA heading above the table.

On the page the rows arrive over the dabadata socket and every change by anyone shows at once. Rows the visitor owns carry a you badge. A write the rules refuse shows the rule's explanation next to the row, for example Not allowed: update notes: denied, any[0] role "subscriber" not in ["administrator","editor"]; any[1] row.author "wp:7" != user "wp:8". An empty table says so; a logged-out visitor on an editable table sees a log-in link instead of the form.

The scripts load only on pages that use the block or the shortcode: the client from the server (<server>/client/dist/dabadata.iife.js) and the plugin's small table script. With JavaScript off the page still lists the rows an anonymous visitor may read, fetched server side with the publishable key and cached for a minute.

Privacy

  • Connecting sends the site's address and the settings page's address to the console (in the browser), and the one-time code, the site's address and the site secret to the server.
  • Live tables: visitors' browsers talk to the dabadata server directly - it sees their IP address and browser like any web request. For a logged-in visitor the token carries wp:<user id>, the display name and the mapped role. Email addresses and passwords are never sent. Rows written through a block are stored in the project with their author's wp:<id>.
  • The server keeps each call in the project's trace and request log for the plan's traceDays and logDays (see Plans and limits), visible to the project's workspace members in the console.
  • The mirror sends only what is listed under What is sent.

The plugin's readme.txt carries the same disclosure for the plugin directory. Uninstalling the plugin deletes its options and transients.

Settings

Everything is in the dabadata_settings option:

SettingMeaning
Server URLWhat PHP calls, e.g. https://dabadata.com. It must be reachable from the WordPress server.
Browser URLOptional. The address browsers use for the server (the live blocks and the admin page's viewer) when it differs from the server URL.
Publishable keypk_live_... / pk_test_..., plain - it is sent to browsers anyway.
Secret keyEncrypted. Used server side only: the mirror, the viewer's view token, the project's roles. Never printed into a page.
Site secretEncrypted. Signs the identity bridge's tokens.
RolesWordPress role to project role.

Manual setup (self-hosted servers): paste the server URL, the publishable key (empty for a self-hosted server's default project) and the secret key - or the server's DABADATA_PROJECT_KEY. For the identity bridge, Generate a site secret and declare it as an issuer in the project file, as the settings page shows:

json
{ "name": "wordpress", "issuer": "https://example.org", "audience": "dabadata",
  "secretEnv": "WORDPRESS_JWT_SECRET", "roleClaim": "role", "defaultRole": "visitor" }

Export settings downloads the server, browser URL, publishable key and role mapping as JSON - never the secret key or the site secret. Import settings takes that JSON on another site, which then still needs Connect (or the secret key pasted by hand).

The connect API

EndpointWhoWhat
POST /v1/connect/codesConsole session, developer or higher, X-Project{ site, return } (http(s), no credentials) -> { code, site, expiresAt }. The code is 64 hex characters, single use, 10 minutes, bound to the environment, the site and the return URL.
POST /v1/connect/exchangeThe code in the body (30 per IP per hour){ code, site, siteSecret } -> { project, env, publishableKey, secretKey, site, issuer, roles }. A used, expired or other site's code answers 400 code_invalid.

The mirror

Beside the blocks, the plugin streams the site's own content changes to dabadata with the secret key, and the dabadata admin menu shows them live.

What it hooks

ComponentWordPress hooks
post:<type>wp_after_insert_post, deleted_post - every post type except internal ones such as revisions, menu items and changesets.
post metaadded_post_meta, updated_post_meta, deleted_post_meta (keys not starting with _), sent as a change on the post.
useruser_register, profile_update, set_user_role, deleted_user
commentwp_insert_comment, edit_comment, transition_comment_status, deleted_comment
term:<taxonomy>created_term, edited_term, delete_term
wc:orderwoocommerce_new_order, woocommerce_update_order, woocommerce_order_status_changed, woocommerce_delete_order, woocommerce_trash_order - only when WooCommerce is active.

Each event says what happened (add, update or remove), to which record, who did it, the record's current fields and, for updates, what changed from what to what. All events of one page load are sent together in one request, when WordPress shuts down, so a save never waits on dabadata.

What is sent, and what never is

  • Posts: status, slug, author, dates, a 200-character excerpt and the content length - not the body.
  • Users: login, display name, roles, registered date.
  • Comments: post, author name, status, type.
  • Every string is cut to 500 characters.

If the server cannot be reached, the site carries on; the failure is written to the PHP error log with a [dabadata] prefix.

The dabadata admin page

The dabadata menu, for administrators, shows whether the server is reachable, the Send snapshot button, and the dabadata viewer embedded in the page. The plugin mints a view token with the secret key for the viewer and caches it for 11 hours, under the token's 12-hour lifetime. The shortcode [dabadata_inspect] puts the same viewer on any page; it renders for administrators only.

In the viewer, the Mirror page is the WordPress flow: every event as it arrives and the current state of each record, grouped by component.

Send snapshot

The plugin only sees changes made after it was activated. Send snapshot backfills everything that already exists - every post, user, term and approved comment - as add events marked snapshot: true, read in batches of 100 and sent in batches of 200. Press it once after installing, and again whenever you want the mirror to match the site exactly.

The mirror API

The plugin speaks the original (v0) part of the API, which sits beside the v1 database:

EndpointWhoWhat
POST /v1/eventsSecret key{ "events": Event[] }, 1 to 500 per call. Each is stored and applied to the record it names, then broadcast to view-token sockets. Answers { "accepted": n }.
GET /v1/eventsView tokenStored events, newest first. limit, before, component.
GET /v1/componentsView tokenEach component with its record count, event count and last activity.
GET /v1/recordsView tokenCurrent records, newest updated first. component, limit.
json
{
  "source": "wordpress",
  "site": "http://localhost:8080",
  "component": "post:page",
  "action": "update",
  "entityId": "42",
  "title": "About us",
  "actor": { "id": "1", "name": "admin", "role": "administrator" },
  "data": { "status": "publish", "slug": "about-us", "author": "1" },
  "changes": { "title": { "from": "About", "to": "About us" } },
  "snapshot": false,
  "ts": 1760000000000
}
dabadata documentation, written against API contract v2.