Repository navigation
DataViews: remove all private API usage #81230
Description
Supersedes #76100
@wordpress/dataviews is a bundled package — it declares neither wpScript nor wpModuleExports in its package.json. Per AGENTS.md:
Avoid using private APIs in bundled packages (packages without
wpScriptorwpModuleExports). Private APIs are intended for Core usage; bundled packages may also be imported via npm into plugin scripts, causing incompatibilities.
Concretely: lockedData is a module-scoped WeakMap and __private is a plain Symbol() — not Symbol.for() (implementation.ts#L187-L193). Two copies of @wordpress/private-apis in one runtime cannot unlock each other's objects, so a plugin that loads wp.components from the WP global alongside a bundled DataViews throws Cannot unlock an object that was not locked before at module-eval time, before anything renders.
Secondary cost: unlock() is typed <T = any>( object: unknown ): T, so every unlocked symbol lands as any. All 23 call sites below are effectively unchecked by TypeScript — swapping any of them for a public equivalent tends to surface pre-existing type mismatches.
@wordpress/components
18 of the 25 symbols in components/src/private-apis.ts, across 21 production files:
| Symbol | Where | Why it's private |
|---|---|---|
Menu |
add-filter.tsx:22, dataviews-view-config/index.tsx:37, dataviews-item-actions/index.tsx:27, column-header-menu.tsx:33, list/index.tsx:62 | Ariakit-based menu; no public replacement exists yet — @wordpress/ui has no menu |
Badge |
picker-grid/index.tsx:36, composite-grid.tsx:45 | A public Badge already ships in @wordpress/ui and DataViews uses it in validation-badge.tsx — this one is a stale holdout |
kebabCase |
dataviews-item-actions/index.tsx:27 | String util, private to avoid committing to a public utils surface |
DateCalendar, DateRangeCalendar |
date.tsx:57, datetime.tsx:24 | New calendar components still stabilizing |
withIgnoreIMEEvents |
richtext/control.tsx:47 | IME keydown wrapper |
Validated* × 12 |
one per control in dataform-controls/ — InputControl, Checkbox, Combobox, Number, Select, Radio, ContentEditable, Textarea, Toggle, ToggleGroup, FormTokenField (+Text in a story) |
The whole validated-form-controls layer is private; DataForm is its only real consumer |
@wordpress/rich-text
5 of the 9 symbols in rich-text/src/private-apis.js, all in one file — dataform-controls/richtext/control.tsx:
| Symbol | Where | Why it's private |
|---|---|---|
useRichText |
richtext/control.tsx:61 | Core editing hook; the public surface is RichText in @wordpress/block-editor, which DataViews can't depend on |
KeyboardShortcutContext, InputEventContext |
richtext/control.tsx:61 | The same context objects that format types' RichTextShortcut / RichTextInputEvent read |
shortcutsListener, inputEventsListener |
richtext/control.tsx:61 | Dispatch the registered callbacks from the editable element's own events |
Exposed by DataViews
| Symbol | Where | Notes |
|---|---|---|
RichTextControl |
locked in dataviews/src/private-apis.ts, unlocked by editor note-form.js:28 |
DataViews' own private export — one symbol, one consumer. Needs to become a public export or move to the consumer |
Todo
-
Badge— DataViews: Use the public@wordpress/uiBadge instead of the private one #81236 -
kebabCase— DataViews: removekebabCaseprivate import by inlining it #81284 and Newkebab-casepackage: extract utility and migrate private API calls #81294 -
withIgnoreIMEEvents— Keycodes: MakewithIgnoreIMEEventsa public API #81343 -
Validated*- Components: Remove ValidatedTextControl private API #80680
- UI: Add Spinner component #81358
- UI: add ControlWithError component #81574
- UI: Add ValidatedInputControl component #81627
- UI: Add TextareaControl component #81359
- UI: Add
ValidatedTextAreacontrol #81984 - Move all other validated components to keep internal in wp-dataviews
- DataViews: vendor
ValidatedNumberControl#81433 - DataViews: vendor
ValidatedCheckboxControl#81435 - DataViews: vendor
ValidatedSelectControl#81391 - DataViews: vendor
ValidatedRadioControl#81434 - DataViews: vendor
ValidatedComboboxControl#81449 - DataViews: vendor
ValidatedToggleGroupControl#81450 - DataViews: vendor
ValidatedFormTokenField#81451 -
ValidatedContentEditableControl: no longer necessary, richtext dependency removed. - DataViews: vendor
ValidatedToggleControl#81492
- DataViews: vendor
- UI: Add
CalendarandRangeCalendar, moved fromcomponentsprivate APIs #81337 - Menu
- DataViews: Move the rich text control into the editor package #81430
- DataViews: Add a lint rule preventing private API imports #81478
Activity
[-]DataViews: remove `@wordpress/components` private API usage[/-][+]DataViews: remove all private API usage[/+]ciampo commentedon Aug 5, 2026
@wordpress/uiwill soon have aMenucomponent :) #79560.DataViews: Inline the kebabCase utility from components
ntsekouras commentedon Aug 7, 2026
Would it make sense to just copy for now in DataViews package since it's the sole consumer of them, and when ready to stabilize move to
uipackage? --cc @WordPress/gutenberg-components72 remaining items
oandregal commentedon Aug 24, 2026
#81984 adds the
ValidatedTextAreacontrol to wordpress/ui and removes the last private API instance from DataViews.oandregal commentedon Aug 24, 2026
Every private API that DataViews used has been removed. 🥳 Thanks everyone for lending a hand to getting this done in 3 weeks.
youknowriad commentedon Aug 24, 2026
Awesome, let's continue the collaboration on potential shortcuts we took to get here.
manzoorwanijk commentedon Aug 25, 2026
Doesn’t this mean that we no longer need the
/wpexport for the package?oandregal commentedon Aug 25, 2026
The
/wpbundle is still necessary for consumers wanting to support different WordPress versions / combinations (Gutenberg enabled/disabled).The issue with using the regular bundle via wordpress/scripts is that some packages are externalized (
@wordpress/componentsbecomeswp.components). The reason this is problematic is thatwp.componentsis defined by the platform (WordPress version, Gutenberg, etc.). Different platforms may have differentwp.componentsversions, but a single DataViews version requires at least thewp.componentsversion it was built against. [1]For example:
NewComponentis added to@wordpress/components.wp.components.NewComponent. [2]The same can happen for updates/removals that affect any
wp.*global used by DataViews. Arguably, public API drift is very rare, but adding new globals can still be problematic (example). By design, private APIs could change at will, so they were more problematic. Now that they're gone, I expect more stability for consumers of the regular bundle as well.[1] I use
wp.componentsas an example, but the same will happen for any package that is externalized.[2] For the sake of brevity, I used major WordPress versions as an example, but the issue can be reproduced with Gutenberg versions. Imagine a consumer that uses the latest npm DataViews package: it may have an error for all platforms that don't have the Gutenberg plugin enabled (because the changes weren't yet published to any major WordPress version).
ciampo commentedon Aug 25, 2026
I think we may have to consider consuming APIs conditionally and enabling features depending on the availability of such APIs?
oandregal commentedon Aug 25, 2026
That's neat. Though, in practice, we'd need to evaluate how feasible this is (we need to understand all the
wp.*globals in use by bundled packages and have mechanisms that tell us about them changing so we can cover).manzoorwanijk commentedon Aug 25, 2026
That means #81843 (comment) is not true and we need those dependencies to be declared.
CC: @youknowriad
ciampo commentedon Aug 25, 2026
Yeah. At the same time, it's potentially very limiting to have dataviews require the same or a later version of their dependencies. In practice, consumers of dataviews will never want to be on latest version in case they support older WordPress versions?
oandregal commentedon Aug 25, 2026
I threw some tokens to create a script that reported the wp global in use by a package:
list-wp-globals.js
FWIW, without having actually checked that there's no under/over-reporting, I ran the script (
list-wp-globals.js packages/dataviews --transitive). Quickly checked by searching, and it seems on a good track (roughly what I expected):youknowriad commentedon Aug 25, 2026
@manzoorwanijk Indeed, I didn't consider previous WP versions support when I made that comment.
@oandregal Why is private-apis still used by bundled packages?
I'm also curious what's the current size difference between
/wpbundle and a regular dataviews bundle. Assuming tree shaking is working properly, the extra globals seems small (maybe outside wp.components where we should probably try to migrate to wordpress/ui as much as possible)oandregal commentedon Aug 25, 2026
I took a look at Manzoor's reference, and I understand the issue is that we copy all wordpress dependencies into the dataviews package (all but a few -date,data, i18n, hooks- that are singleton). This means transitive dependencies become actual dataviews dependencies that should be listed in dataviews'
package.json.I am still looking at this, but I wonder: now that the private APIs are removed, is it possible stop copying these wordpress deps into dataviews and use them as npm packages (no wp globals)? This would stop demanding we add those transitive dependencies. Though I suppose we may have issues with transitive private dependencies. Is it an actual issue in practice? I'm learning and looking into this, happy to hear what folks think.
youknowriad commentedon Aug 25, 2026
How is this different than the
/wpbundle?To clarify my question, the
/wpbundle exist to do this basically. Bundlers (like wp.scripts) can't say "bundle all the dependencies of this particular package aka dataviews but externalize the dependencies of this other script (say a custom third party block using wordpress/components) and this is why we offer a pre-bundled/wpoandregal commentedon Aug 31, 2026
For awareness, we reverted the removal of dataviews from the private-api package allowlist, as that prevents older dataviews versions from accessing private APIs: #82221 (it has been backported to Gutenberg 23.9).