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 currently supports resources and groups. New entity types (e.g., notes, 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' or 'group'
noteIdnumberOptional. Note ID for "Note's Resources" tab
existingIdsnumber[]IDs already selected (shown as "Added")
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:

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

note: {
entityType: 'note',
entityLabel: 'Notes',
searchEndpoint: '/v1/notes',
searchParams: (query, filters, maxResults) => {
const params = new URLSearchParams({ MaxResults: String(maxResults) });
if (query) params.set('name', query);
if (filters.noteType) params.set('noteTypeId', filters.noteType);
return params;
},
filters: [
{ key: 'noteType', label: 'Note Type', endpoint: '/v1/note/noteTypes', multi: false }
],
tabs: null,
renderItem: 'noteCard', // Add new render mode
gridColumns: 'grid-cols-2 md:grid-cols-3',
getItemId: (item) => item.ID,
getItemLabel: (item) => item.Name
}
};

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, and maxResults
filtersarrayFilter definitions (see below)
tabsarray|nullTab definitions (null for no tabs)
renderItemstringRender mode: 'thumbnail' or 'groupCard'
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');