<Slot> in their right-hand sidebar — typically named <resource>.form_sidebar. Widgets rendered into these slots see the current record and can fetch their own data without disturbing the host page.
This recipe builds a “Loyalty status” card for the customer detail page: read-only, fetches the customer’s points + tier, shows a spinner while loading, an empty state when there’s no data, and hides itself for staff without the right permission.
The component
storeIdin the query key. The dashboard is multi-store; without this, switching stores leaves stale data on screen.staleTime: 30_000keeps the data fresh enough for a sidebar widget without re-fetching on every focus event.- Three states. Loading (
<Skeleton>), empty (<Empty>), and loaded. Skipping the empty state ships a card that says “0 points” when really the customer has never been enrolled — different meaning, different UX. - Strings are hardcoded for brevity. Real widgets should go through i18n — see Translations.
Register the widget
usePermissions) rather than in an if predicate on the entry — the predicate only receives the slot context today, not ambient permissions (see the slots catalog).
position: 50 puts the card near the top of the sidebar (built-in cards typically use 100/200/300). Pick a number based on whether your widget is more or less important than the others — for a “status” card, near the top is right.
When to fetch in the widget vs. read from the host
The pattern above fetches its own data. The alternative is to read it from the resource the host passes:| Data | Approach |
|---|---|
Already on the host’s serializer (e.g., customer.tags) | Read from the slot context — no extra HTTP |
| Conditional, expensive, or owned by your plugin | Fetch in the widget |
| Sometimes-needed (e.g., gated by feature flag) | Fetch in the widget |
Loading states for the host page
The built-in detail pages render their sidebar only after the record has loaded, so the context record is always set when your widget mounts. If you register into a slot where that’s not guaranteed (check the slots catalog entry), guard defensively:Reference
- Slots catalog → form sidebars — every page-level sidebar slot
useQuery— query key conventions@spree/dashboardREADME — the multi-store model and whystoreIdbelongs in the key

