Skip to content

Your EMCP guide

Answers from our docs

YOUR CHAT, YOUR CHOICE

Before we chat

MSR Builds processes your question and up to four recent messages on our server to answer from the EMCP docs. AI is optional and needs your separate permission.

The chat is kept in this page's memory, not saved as a conversation in our database or browser storage. Our hosting and security services process connection information, such as your IP address, to prevent abuse.

Cloudflare Turnstile checks your browser and connection to prevent automated abuse when you send a question. No login is required.

Please leave out personal, sensitive or confidential information, passwords and API keys.

You can withdraw consent and clear this page's chat at any time. This cannot undo a request already sent or erase provider/security records.

Chat privacy, retention & your rights ↗ · Privacy contact

Starting chat consents to this processing. You can browse our docs without using the chat.

Database

Inspect the WordPress database with flexible read-only SQL, and, when you opt in, perform structured, parameterized writes. The 3 read tools are enabled by default; the 3 write tools ship disabled-by-default (an admin opts in on EMCP Tools → Tools). All six require manage_options.

Direct DB access. The read path (query) is bounded by a read-only-SQL validator that rejects writes, DDL, stacked statements, MySQL /*! … */ executable comments, and file-access SQL (INTO OUTFILE / LOAD_FILE). Writes are structured and parameterized via $wpdb (no raw write-SQL, so no DDL), refuse wp_users/wp_usermeta, force a non-empty WHERE on update/delete, accept only real column names, and record each touched row in History so the write can be undone. Arbitrary DB access is contrary to WordPress.org plugin guidelines; this feature exists per an explicit project decision and stays off unless you enable it.

List all tables in the WordPress database with row counts and sizes. Read-only (manage_options).

Return the column definitions and indexes for a table. Read-only (manage_options).

Run a read-only SQL query: SELECT / SHOW / DESCRIBE / EXPLAIN only; results are capped at 1,000 rows. Writes, DDL, stacked statements, /*! executable comments, and file-access SQL are rejected. It also refuses reads that touch wp_users/wp_usermeta (password hashes, session tokens, activation keys), returning a protected_read error, even for a plain SELECT; use list-users/get-user for user data instead. Read-only (manage_options).

Hardened in v3.13.0. The guard was rebuilt around a real SQL tokenizer, not a string pattern-match, closing several bypasses a security review found (a comment trick, a backslash inside a string, a backquoted alias, a double-quoted identifier; each could hide a table name from the old check). A query is analyzed under every way the server could read it and refused if any reading is unsafe. Since 3.13.0, query also refuses:

  • The server’s own system schemas: mysql, information_schema, performance_schema, sys (protected_read, same error code and filter, emcp_tools_db_protected_tables, as the user-table refusal above).
  • Variable assignment (:=) inside the query, since that’s a write in disguise (not_read_only).
  • Delay/lock functions: SLEEP, BENCHMARK, GET_LOCK, and similar (unsafe_function_blocked).

Row limiting is enforced by the database itself now, not by fetching everything and slicing in PHP: a query with no top-level LIMIT gets one added; a LIMIT larger than 1,000 is refused (limit_too_large) rather than silently capped; and a LIMIT that only appears inside a subquery doesn’t count as bounding the outer query. Each query also runs under a 10-second server-side statement timeout.

Every write tool uses $wpdb with parameterized values (no raw write-SQL or DDL) and refuses wp_users/wp_usermeta. The refused-table list (wp_users/wp_usermeta by default) is the same one the read-only query tool checks above, both filterable via emcp_tools_db_protected_tables. Since 3.20.0 every column name in data and where must be a real column of the table, or the call is refused with unknown_column (read describe-table for the names).

Undo from History (3.20.0). Each write locks the rows it touches and records them by primary key, with their previous values, in the same transaction as the write. History can then undo it: deleted rows come back under their original ids, an inserted row is removed, and a row changed since is a conflict that only Undo anyway overwrites. Every answer includes undoable and change_id, plus not_undoable with the reason when History cannot undo it: a table without a primary key, a table that is not InnoDB, or an update that changes primary key values. Those writes still run. A write that would change more than 500 rows is refused with too_many_rows, and nothing is changed.

Insert a row into a table using $wpdb. Returns insert_id, affected and the undo fields (manage_options).

Update rows matching a forced non-empty equality WHERE. Undoable from History unless the update changes primary key values; refused above 500 rows (manage_options).

Delete rows matching a forced non-empty equality WHERE. Requires an explicit confirm:true. Undoable from History, with rows restored under their original keys; refused above 500 rows (manage_options).

insert-row, update-rows, and delete-rows are off until you enable them. Open EMCP Tools → Tools in wp-admin, go to the WordPress tab, find the Database section, switch on the ones you want, then choose Save changes in the save bar at the bottom of the screen. See Disabling tools.