Skip to main content

Entity Picker

The entity picker is a reusable modal component for selecting entities in the block editor and other UI contexts. It ships with configurations for resources, groups, and notes. The shipped page UI opens only the resource and group pickers directly, but a plugin action's entity_ref param can open the picker for any of resource, note, or group. New entity types (e.g., tags, categories) can be added through configuration objects without modifying core logic.

Using the Picker

From Block Components

Open the picker from any block component using the Alpine store:

openPicker() {
Alpine.store('entityPicker').open({
entityType: 'resource', // 'resource' or 'group'
noteId: this.noteId, // optional, for resource context
existingIds: this.selectedIds,
onConfirm: (selectedIds) => {
this.handleSelection(selectedIds);
}
});
}

Configuration Options

OptionTypeDescription
entityTypestringRequired. Entity type key: 'resource', 'group', or 'note'
noteIdnumberOptional. Note ID for "Note's Resources" tab
existingIdsnumber[]IDs already selected (shown as "Added")
lockedFiltersobjectOptional. Filter values forced on every search (passed into searchParams), not user-editable. Defaults to {}
multiSelectbooleanOptional. Whether more than one entity can be selected at once. Defaults to true
onConfirmfunctionCallback receiving array of selected IDs

Multi-Selection

The picker supports selecting multiple entities at once. Click items to toggle their selection, then confirm the entire selection with the confirm button.

Adding New Entity Types

The picker is designed to be extensible. To add support for a new entity type (e.g., notes, tags):

1. Add Configuration

Edit src/components/picker/entityConfigs.js. A note configuration (with a noteCard render mode) already ships in this file as a complete worked example. No shipped page UI opens the note picker directly, though a plugin action's entity_ref param can select entityType: 'note' (as well as 'resource' or 'group'). Use it as a reference for the shape of a config object:

export const entityConfigs = {
// existing configs...

note: {
entityType: 'note',
entityLabel: 'Notes',
searchEndpoint: '/v1/notes',
maxResults: 50,
searchParams: (query, filters, lockedFilters = {}, maxResults) => {
const params = new URLSearchParams({ MaxResults: String(maxResults) });
if (query) params.set('name', query);
if (filters.tags) filters.tags.forEach(id => params.append('Tags', id));
if (lockedFilters.note_type_ids) {
lockedFilters.note_type_ids.forEach(id => params.append('NoteTypeIds', id));
}
return params;
},
filters: [
{ key: 'tags', label: 'Tags', endpoint: '/v1/tags', multi: true }
],
tabs: null,
renderItem: 'noteCard',
gridColumns: 'grid-cols-2 md:grid-cols-3 lg:grid-cols-4',
getItemId: (item) => item.ID,
getItemLabel: (item) => item.Name || `Note ${item.ID}`
}
};

The searchParams function takes four arguments in order: query, filters, lockedFilters, and maxResults.

2. Add Render Mode (if needed)

If your entity needs a custom card display, add a new render mode to templates/partials/entityPicker.tpl:

{# Results grid - Note cards #}
<div x-show="... && $store.entityPicker.config?.renderItem === 'noteCard'"
...>
<template x-for="item in $store.entityPicker.displayResults" ...>
<!-- Custom card markup -->
</template>
</div>

3. Add Metadata Fetcher (optional)

If blocks need to display entity metadata, add a fetcher to src/components/picker/entityMeta.js:

async function fetchNoteMeta(ids) {
const meta = {};
// ... fetch logic
return meta;
}

// Add to fetchers map
const fetchers = {
resource: fetchResourceMeta,
group: fetchGroupMeta,
note: fetchNoteMeta // Add this
};

Architecture

src/components/picker/
├── entityConfigs.js # Entity type configurations
├── entityPicker.js # Alpine store with picker logic
├── entityMeta.js # Metadata fetching utilities
└── index.js # Module exports

templates/partials/
└── entityPicker.tpl # Modal template

The picker is entity-agnostic. All entity-specific behavior comes from configuration objects.

Configuration Reference

Entity Config Properties

PropertyTypeDescription
entityTypestringUnique identifier for this entity type
entityLabelstringDisplay name for the modal title
searchEndpointstringAPI endpoint for searching entities
maxResultsnumberMaximum results to fetch (default: 50)
searchParamsfunctionBuilds URLSearchParams from query, filters, lockedFilters, and maxResults
filtersarrayFilter definitions (see below)
tabsarray|nullTab definitions (null for no tabs)
renderItemstringRender mode: 'thumbnail', 'groupCard', or 'noteCard'
gridColumnsstringTailwind grid classes for results layout
getItemIdfunctionExtracts ID from entity object
getItemLabelfunctionExtracts display label from entity object

Filter Definition

{
key: 'tags', // Key used in filterValues
label: 'Tags', // Display label
endpoint: '/v1/tags', // Autocomplete suggestions endpoint
multi: true // Allow multiple selections
}

Tab Definition

{
id: 'note', // Tab identifier
label: "Note's Resources" // Display label
}

Events

The picker dispatches a custom event when closed:

window.dispatchEvent(new CustomEvent('entity-picker-closed'));

Filter autocompleters listen for this event to reset their state.

Search Behavior

  • Debouncing: Search input is debounced by 200ms to reduce API calls during typing
  • Request aborting: Each new search cancels the previous in-flight request, preventing stale results from overwriting newer ones

Metadata Caching

Entity metadata fetched for display in blocks is cached in memory to avoid redundant API requests. The cache has a 5-minute TTL and is automatically managed.

Cache Behavior

  • Metadata is cached per entity (keyed by entityType:id)
  • Cache entries expire after 5 minutes
  • Batched concurrency: maximum 5 concurrent metadata requests per batch
  • Failed requests retry up to 2 times with 500ms linear backoff
  • 4xx errors (client errors) are not retried
  • Cache is cleared on page reload

Clearing the Cache

If you need to force-refresh metadata (e.g., after editing an entity):

import { clearMetaCache } from './components/picker/index.js';

// Clear all cached metadata
clearMetaCache();

// Clear only group metadata
clearMetaCache('group');

// Clear only resource metadata
clearMetaCache('resource');