File: C:/Users/fred/.codex/.tmp/plugins/plugins/wix/skills/wix-app/references/DASHBOARD_MODAL.md
# Wix Dashboard Modal
Dashboard modals are popup dialogs triggered from dashboard pages or plugins. They use the Dashboard SDK for lifecycle control via `openModal()` and `closeModal()`.
## Scaffold
Use `wix generate --params` with all required fields:
```bash
wix generate --params '{"extensionType":"DASHBOARD_MODAL","title":"<title>","folder":"<folder>"}'
```
| Field | Constraint |
| --- | --- |
| `title` | Display name for the modal. |
| `folder` | Lowercase alphanumeric and hyphens. |
The CLI generates the folder, the modal `.tsx`, the config file, the builder file, the UUID, and the `src/extensions.ts` registration. After scaffolding, implement the modal UI in the generated `.tsx`.
## Quick Reference
| Task | Method | Example |
|------|--------|---------|
| Open modal | `dashboard.openModal()` | `openModal({ modalId: "modal-id" })` |
| Pass data to modal | `params` in `openModal()` | `params: { userId: "123" }` |
| Read data in modal | `observeState()` | `dashboard.observeState((state) => { ... })` |
| Close modal | `dashboard.closeModal()` | `closeModal()` |
| Return data to parent | Pass data to `closeModal()` | `closeModal({ ... })` |
| Wait for modal close | `modalClosed` Promise | `const { modalClosed } = openModal(...);` |
## Opening a Modal
```typescript
import { dashboard } from "@wix/dashboard";
// Simple open
const result = await dashboard.openModal({
modalId: "your-modal-id", // The id generated by the CLI for this modal
});
// Pass data to modal via params
const result = await dashboard.openModal({
modalId: "your-modal-id",
params: {
userId: user.id,
itemData: complexObject, // Objects are passed directly, no encoding needed
},
});
// Get notified when the modal is closed
const { modalClosed } = dashboard.openModal({
modalId: "your-modal-id",
});
const result = await modalClosed; // Resolves with data from closeModal()
```
## Receiving Data in Modal
Inside the modal, subscribe via `dashboard.observeState()` to access whatever was passed in `openModal({ params })`. The callback receives the params object as `state`:
```typescript
import { dashboard } from "@wix/dashboard";
dashboard.observeState((state) => {
// state contains the keys you passed in `openModal({ params: { ... } })`
console.log(state.userId, state.itemData);
});
```
Call it inside a `useEffect` if you want to set local React state from the params.
## Closing Modal
Call `closeModal()` from within the modal extension. The optional argument is data passed back to the opener (resolved via `modalClosed`).
```typescript
import { dashboard } from "@wix/dashboard";
dashboard.closeModal({ saved: true, itemId: "123" }); // arg is optional
```
The argument must be cloneable via the [structured clone algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm) — strings, numbers, booleans, plain objects, arrays, Dates, Maps, Sets, ArrayBuffers. **Not supported:** functions, DOM nodes, class instances with methods, Symbols, Promises.
## Customizing Modal
Edit `<modal>.config.ts` (generated alongside the modal) to change the title and dimensions. The generated `.tsx` already imports and uses it.
```typescript
// <modal>.config.ts
export default {
title: 'User Settings',
width: 600,
height: 500,
};
```
## Common Mistakes
| Mistake | Fix |
|---------|-----|
| Can't find modal ID | Check the modal's generated builder file (`extensions.ts`) `id` field |
| Using `extensionId` instead of `modalId` | Use `modalId` in `openModal()` |
| Can't access params in modal | Use `dashboard.observeState()` to read passed data |
| Modal won't close | Use `dashboard.closeModal()` from `@wix/dashboard` |
## Real-World Example
End-to-end edit-item flow. The scaffolded `<modal>.tsx` already wires up `CustomModalLayout` — the unique parts are on the opener side, in `observeState`, and in the save handler.
```typescript
// Dashboard Page: open the modal with the item to edit
const handleEdit = (item: Item) => {
dashboard.openModal({
modalId: "edit-item-modal-guid",
params: { item }, // objects are passed directly via params
});
};
// Modal: read params, save, toast, close
const [formData, setFormData] = useState<Item | null>(null);
useEffect(() => {
dashboard.observeState((state) => {
if (state.item) setFormData(state.item);
});
}, []);
const handleSave = async () => {
// ...your save logic...
dashboard.showToast({ message: "Saved!", type: "success" });
dashboard.closeModal();
};
```