# Widget Workflows Guide for Modyo MCP

> **Critical Guide**: Widget IDs change after publishing, breaking automation workflows. This guide shows how to work with widgets safely using stable UUIDs.

---

## Table of Contents

1. [The Widget ID Instability Problem](#the-widget-id-instability-problem)
2. [Widget IDs vs UUIDs](#widget-ids-vs-uuids)
3. [Safe Widget Development Workflow](#safe-widget-development-workflow)
4. [Widget Variables Pattern](#widget-variables-pattern)
5. [Publishing Workflow](#publishing-workflow)
6. [Adding Widgets to Pages](#adding-widgets-to-pages)
7. [Common Pitfalls](#common-pitfalls)

---

## The Widget ID Instability Problem

### ⚠️ Critical Issue Discovered (Iterations 8, 9)

**Widget IDs change after publishing**, causing:
- Broken references in automation scripts
- Need to re-fetch widget list after every publish
- Cannot reliably track widgets across publish cycles
- Workflow friction

### Example of the Problem

```typescript
// PROBLEMATIC WORKFLOW

// 1. Create widget
const widget = await widget-definitions-create({
  platformSlug: "fed-team",
  siteId: 4612,
  widgetName: "Hero Banner"
})
// Returns: { id: 86885, uuid: "1ad1563f..." }

// 2. Update widget
await widget-definition-update({
  widgetId: 86885,  // Use ID from create
  html: "<div>...</div>",
  css: "...",
  js: ""
})

// 3. Publish widget
await release-create({
  data: {
    widgetDefinition: [{ id: 86885, selected: true }]
  }
})

// 4. Add to page
await page-update({
  widgets: [
    { definition_uuid: "1ad1563f...", column: 0, position: 0 }
  ]
})

// 5. Later, try to update widget
await widget-definition-update({
  widgetId: 86885,  // ❌ WRONG - ID changed to 86891 after publish
  html: "<div>Updated</div>"
})
// ❌ FAILS or updates wrong widget
```

### What Changes After Publishing

| Before Publish | After Publish |
|----------------|---------------|
| ID: 86885 | ID: 86891 ⚠️ |
| UUID: 1ad1563f... | UUID: 1ad1563f... ✅ |
| Status: draft | Status: published |

**Key Insight**: UUIDs are stable, IDs are not.

---

## Widget IDs vs UUIDs

### Widget ID (Volatile)

- **Changes after publish**
- Used in MCP tool parameters
- Must be re-fetched after publish
- Example: `86885` → `86891`

### Widget UUID (Stable)

- **Never changes**
- Persists across publish cycles
- Used in page widget definitions
- Example: `1ad1563f10e5d38fc15b31be3b59098a82c802eb`

### When to Use Each

| Operation | Use | Example |
|-----------|-----|---------|
| Update widget definition | ID | `widget-definition-update({ widgetId: 86891 })` |
| Delete widget | ID | `widget-definition-delete({ widgetId: 86891 })` |
| Get widget details | ID | `widget-definition-get({ widgetId: 86891 })` |
| Add widget to page | UUID | `page-update({ widgets: [{ definition_uuid: "1ad1563f..." }] })` |
| Track widget across publishes | UUID | Store UUID in automation scripts |

---

## Safe Widget Development Workflow

### ✅ Complete Widget Creation Workflow

```typescript
// STEP 1: Create widget definition
const createResult = await widget-definitions-create({
  platformSlug: "fed-team",
  siteId: 4612,
  widgetName: "Hero Banner Carousel"
})

// Store BOTH for later use
const initialId = createResult.id          // e.g., 86885
const stableUuid = createResult.uuid       // e.g., "1ad1563f..."
console.log(`Widget created: ID=${initialId}, UUID=${stableUuid}`)

// STEP 2: Update widget with HTML, CSS, JS
await widget-definition-update({
  platformSlug: "fed-team",
  siteId: 4612,
  widgetId: initialId,  // Use ID from create
  html: `
    {% assign entries = spaces[widget.variables.space_uid].types[widget.variables.type_uid].entries %}
    <div class="carousel">
      {% for entry in entries %}
        <div class="slide">{{ entry.fields['Title'] }}</div>
      {% endfor %}
    </div>
  `,
  css: `
    .carousel { position: relative; }
    .slide { padding: 2rem; }
  `,
  js: ""  // Always include js, even if empty
})

// STEP 3: Add widget variables
await widget-definitions-variable-create({
  platformSlug: "fed-team",
  siteId: 4612,
  widgetId: initialId,
  slug: "space_uid",
  global_variable_values_attributes: [
    { lang: "en", value: "public-content", default: true }
  ]
})

await widget-definitions-variable-create({
  platformSlug: "fed-team",
  siteId: 4612,
  widgetId: initialId,
  slug: "type_uid",
  global_variable_values_attributes: [
    { lang: "en", value: "hero_banner", default: true }
  ]
})

// STEP 4: Get publishable elements
const elements = await release-get-elements-to-publish({
  platformSlug: "fed-team",
  siteId: 4612
})

// Find widget in publishable list
const widgetToPublish = elements.widgetDefinitions.find(w =>
  w.uuid === stableUuid
)

// STEP 5: Publish widget
await release-create({
  platformSlug: "fed-team",
  siteId: 4612,
  data: {
    widgetDefinition: [{ id: widgetToPublish.id, selected: true }]
  }
})

// STEP 6: ⚠️ RE-FETCH WIDGET LIST - IDs CHANGED
const publishedWidgets = await widget-get-custom-widgets({
  platformSlug: "fed-team",
  siteId: 4612
})

// Find widget by STABLE UUID
const publishedWidget = publishedWidgets.find(w => w.uuid === stableUuid)
const newId = publishedWidget.id  // e.g., 86891 (changed from 86885)

console.log(`Widget published: ID changed ${initialId} → ${newId}`)
console.log(`UUID stable: ${stableUuid}`)

// STEP 7: Use NEW ID for any future updates
// But use STABLE UUID for adding to pages
```

### The Golden Rule

**After publishing widgets, ALWAYS re-fetch the widget list to get current IDs.**

---

## Widget Variables Pattern

### Why Use Variables?

Variables make widgets reusable across different content:

```liquid
<!-- WITHOUT variables (hardcoded) -->
{% assign entries = spaces['public-content'].types['hero_banner'].entries %}
<!-- Only works for hero_banner type -->

<!-- WITH variables (reusable) -->
{% assign entries = spaces[widget.variables.space_uid].types[widget.variables.type_uid].entries %}
<!-- Works for ANY space/type by changing variables -->
```

### Creating Widget Variables

```typescript
// Variable for space UID
await widget-definitions-variable-create({
  platformSlug: "fed-team",
  siteId: 4612,
  widgetId: 86885,
  slug: "space_uid",
  active: true,
  global_variable_values_attributes: [
    {
      lang: "en",
      value: "public-content",  // Default value
      default: true
    },
    {
      lang: "es",
      value: "public-content",
      default: false
    }
  ]
})
```

### Common Widget Variables

| Variable | Purpose | Example Values |
|----------|---------|----------------|
| `space_uid` | Content space to fetch from | `"public-content"`, `"blog"` |
| `type_uid` | Content type to fetch | `"hero_banner"`, `"product_card"` |
| `limit` | Number of items to show | `3`, `6`, `12` |
| `category_filter` | Filter by category | `"featured"`, `"all"` |
| `show_featured_only` | Only show featured items | `true`, `false` |
| `section_title` | Heading text | `"Our Products"`, `"Latest News"` |

### Using Variables in Widget HTML

```liquid
<!-- Access widget variables -->
{% assign space_uid = widget.variables.space_uid | default: 'public-content' %}
{% assign type_uid = widget.variables.type_uid | default: 'hero_banner' %}
{% assign limit = widget.variables.limit | default: 3 %}
{% assign section_title = widget.variables.section_title | default: 'Section' %}

<!-- Use variables -->
<div class="widget">
  <h2>{{ section_title }}</h2>

  {% assign entries = spaces[space_uid].types[type_uid].entries | limit: limit %}

  {% for entry in entries %}
    <div class="item">{{ entry.fields['Title'] }}</div>
  {% endfor %}
</div>
```

---

## Publishing Workflow

### Two-Step Publishing Process

**Step 1: Get Publishable Elements**
```typescript
const elements = await release-get-elements-to-publish({
  platformSlug: "fed-team",
  siteId: 4612
})

// Check what widgets are ready
console.log("Publishable widgets:")
elements.widgetDefinitions.forEach(w => {
  console.log(`- ${w.name} (ID: ${w.id}, UUID: ${w.uuid}, Status: ${w.status})`)
})
```

**Step 2: Publish Selected Widgets**
```typescript
await release-create({
  platformSlug: "fed-team",
  siteId: 4612,
  data: {
    widgetDefinition: [
      { id: 86885, selected: true },
      { id: 86886, selected: true }
    ]
  }
})
```

### Publishing Multiple Related Items

Publish widgets + page together:

```typescript
await release-create({
  platformSlug: "fed-team",
  siteId: 4612,
  data: {
    widgetDefinition: [
      { id: 86885, selected: true }  // Hero Banner widget
    ],
    page: [
      { id: 20543, selected: true }   // Home page using the widget
    ]
  }
})
```

### After Publishing Checklist

- [ ] ✅ Re-fetch widget list: `widget-get-custom-widgets`
- [ ] ✅ Update stored IDs in automation scripts
- [ ] ✅ Verify widgets appear in custom widgets list
- [ ] ✅ Test adding widget to a page

---

## Adding Widgets to Pages

### Using UUID (Stable Identifier)

```typescript
// CORRECT: Use UUID to add widget to page
await page-update({
  platformSlug: "fed-team",
  siteId: 4612,
  pageId: 20543,
  layout_page: {
    widgets: [
      {
        type: "custom_widget",
        definition_uuid: "1ad1563f10e5d38fc15b31be3b59098a82c802eb",  // STABLE
        column: 0,
        position: 0,
        sync: false,  // Async loading (recommended)
        variables: [
          { slug: "space_uid", value: "public-content" },
          { slug: "type_uid", value: "hero_banner" },
          { slug: "limit", value: "3" }
        ]
      }
    ]
  }
})
```

### Widget Properties

| Property | Required | Description | Example |
|----------|----------|-------------|---------|
| `type` | Yes | Widget type | `"custom_widget"` |
| `definition_uuid` | Yes | Stable widget identifier | `"1ad1563f..."` |
| `column` | Yes | Grid column (0-based) | `0`, `1`, `2` |
| `position` | Yes | Position in column | `0`, `1`, `2` |
| `sync` | No | Loading mode | `true` (blocking), `false` (async) |
| `variables` | No | Override default variables | `[{ slug, value }]` |
| `title` | No | Custom widget title | `"Featured Products"` |

### Widget Loading Modes

**Synchronous (`sync: true`)**:
- Widget renders during page load
- Blocks page rendering until widget completes
- Use for critical above-the-fold content

**Asynchronous (`sync: false`)** - **Recommended**:
- Widget loads after page renders
- Non-blocking, better performance
- Use for below-the-fold content

```typescript
// Example: Mix sync and async widgets
widgets: [
  {
    definition_uuid: "1ad1563f...",  // Hero (above fold)
    column: 0,
    position: 0,
    sync: true   // Synchronous - loads first
  },
  {
    definition_uuid: "ebd1ea08...",  // Products (below fold)
    column: 0,
    position: 1,
    sync: false  // Asynchronous - loads after
  }
]
```

---

## Common Pitfalls

### ❌ Pitfall 1: Using Stale Widget ID After Publish

```typescript
// WRONG
const widgetId = 86885
await release-create({ data: { widgetDefinition: [{ id: widgetId }] } })

// Later... (ID has changed to 86891)
await widget-definition-update({ widgetId: 86885, ... })  // ❌ FAILS
```

**Fix**: Re-fetch widget list after publishing:
```typescript
// CORRECT
const widgetId = 86885
await release-create({ data: { widgetDefinition: [{ id: widgetId }] } })

// Re-fetch
const widgets = await widget-get-custom-widgets({ ... })
const newId = widgets.find(w => w.uuid === "1ad1563f...").id

// Use new ID
await widget-definition-update({ widgetId: newId, ... })
```

### ❌ Pitfall 2: Missing `js` Parameter in widget-definition-update

```typescript
// WRONG - omitting js parameter
await widget-definition-update({
  widgetId: 86885,
  html: "...",
  css: "..."
  // Missing js!
})
```

**Fix**: Always include all three parameters:
```typescript
// CORRECT
await widget-definition-update({
  widgetId: 86885,
  html: "...",
  css: "...",
  js: ""  // Include even if empty
})
```

**Why**: The tool requires all three properties. Omitting `js` causes update to fail or revert to previous version.

### ❌ Pitfall 3: Using ID Instead of UUID in page-update

```typescript
// WRONG - using widget ID instead of UUID
widgets: [
  {
    widget_id: 86891,  // ❌ WRONG property
    column: 0,
    position: 0
  }
]
```

**Fix**: Use `definition_uuid`:
```typescript
// CORRECT
widgets: [
  {
    type: "custom_widget",
    definition_uuid: "1ad1563f...",  // ✅ Stable UUID
    column: 0,
    position: 0
  }
]
```

### ❌ Pitfall 4: Not Publishing Widget Before Adding to Page

```typescript
// WRONG
await widget-definitions-create({ widgetName: "Hero" })
await page-update({ widgets: [{ definition_uuid: "..." }] })  // ❌ Widget not published
```

**Fix**: Publish widget first:
```typescript
// CORRECT
const widget = await widget-definitions-create({ widgetName: "Hero" })
await widget-definition-update({ widgetId: widget.id, html, css, js })
await release-create({ data: { widgetDefinition: [{ id: widget.id }] } })

// Now add to page
await page-update({ widgets: [{ definition_uuid: widget.uuid }] })
```

### ❌ Pitfall 5: Hardcoding Content in Widget HTML

```liquid
<!-- WRONG - hardcoded -->
{% assign entries = spaces['public-content'].types['hero_banner'].entries %}
<!-- Not reusable for other content types -->

<!-- CORRECT - uses variables -->
{% assign entries = spaces[widget.variables.space_uid].types[widget.variables.type_uid].entries %}
<!-- Reusable by changing variables -->
```

---

## Widget Development Checklist

Before publishing a widget:

- [ ] ✅ HTML includes proper Liquid syntax
- [ ] ✅ Field names use bracket notation: `fields['Name']`
- [ ] ✅ Optional fields have conditional rendering
- [ ] ✅ Empty states provided for collections
- [ ] ✅ Widget variables used for reusability
- [ ] ✅ CSS includes CSP-safe styles (no inline styles on elements)
- [ ] ✅ JS parameter included (even if empty)
- [ ] ✅ Accessibility attributes added (ARIA, alt text)
- [ ] ✅ Responsive design tested
- [ ] ✅ Variables have sensible defaults

After publishing a widget:

- [ ] ✅ Re-fetch widget list to get new ID
- [ ] ✅ Update any stored IDs
- [ ] ✅ Test adding widget to page with UUID
- [ ] ✅ Verify widget appears in custom widgets list
- [ ] ✅ Test on live site

---

## UUID Lookup Pattern (Future Enhancement)

**Current Limitation**: No tool to get widget by UUID directly.

**Workaround**: Filter widget list by UUID:
```typescript
// Get all published widgets
const widgets = await widget-get-custom-widgets({
  platformSlug: "fed-team",
  siteId: 4612
})

// Find by UUID
const targetWidget = widgets.find(w =>
  w.uuid === "1ad1563f10e5d38fc15b31be3b59098a82c802eb"
)

if (targetWidget) {
  console.log(`Widget ID: ${targetWidget.id}`)
  // Use ID for updates
  await widget-definition-update({ widgetId: targetWidget.id, ... })
}
```

**Suggested Improvement** (Phase 6):
```typescript
// Future tool: widget-get-by-uuid
const widget = await widget-get-by-uuid({
  platformSlug: "fed-team",
  siteId: 4612,
  uuid: "1ad1563f10e5d38fc15b31be3b59098a82c802eb"
})
// Returns current widget with up-to-date ID
```

---

## Related Documentation

- [Liquid Best Practices](../../channels/liquid/best-practices.md) - Field name notation and Liquid patterns
- [Template Workflows Guide](../../channels/templates/workflows.md) - Template ID stability
- [CSS Organization Guide](../../channels/css-js/organization.md) - CSS templates vs snippets

---

**Last Updated**: October 23, 2025
**Source**: Iterations 8, 9 findings from mcp-improvements.md
