## Accessing Content in Channels (Liquid Markup)

### Overview

Modyo Channels can access Content API data directly in Liquid templates. This enables server-side rendering of content entries, combining the headless CMS capabilities of Content with the templating power of Channels.

**Key Concept**: Content created via Content tools (spaces, types, entries) can be accessed in Channels templates using the `spaces` object.

**Available in all template contexts**:
- ✅ **Snippets** (system snippets like `header`, `footer`)
- ✅ **Custom Snippets** (user-created reusable components)
- ✅ **Widgets** (custom widget HTML/JS)
- ✅ **Content Pages** (entry detail pages, category pages)
- ✅ **Layouts** (page wrappers)
- ✅ **Templates** (CSS, JS templates)

The `spaces` object is globally available in all Liquid rendering contexts.

---

### Basic Syntax

**Accessing Entries**:
```liquid
{% assign entries = spaces['SPACE_UID'].types['TYPE_UID'].entries %}

<ul>
{% for entry in entries %}
  <li>{{ entry.meta.title }}</li>
{% endfor %}
</ul>
```

**Components**:
- `spaces['SPACE_UID']` - Access specific space by UID (not ID)
- `.types['TYPE_UID']` - Access content type by UID (not ID)
- `.entries` - Get all entries of that type

---

### Entry Structure

**Entry Object Properties**:

```liquid
{% assign entry = spaces['my-bank'].types['product'].entries | first %}

<!-- Metadata (always available) -->
{{ entry.meta.uuid }}           <!-- Entry UUID (stable across publishes) -->
{{ entry.meta.title }}          <!-- Entry name/title -->
{{ entry.meta.slug }}           <!-- URL-friendly slug -->
{{ entry.meta.created_at }}     <!-- Creation timestamp -->
{{ entry.meta.updated_at }}     <!-- Last update timestamp -->
{{ entry.meta.published_at }}   <!-- Publication timestamp -->
{{ entry.meta.category }}       <!-- Category object -->
{{ entry.meta.tags }}           <!-- Array of tag strings -->

<!-- Custom Fields (defined in content type) -->
{{ entry.fields['Field Name'] }}  <!-- MUST use exact name from Content Type -->
```

**Important**:
- **Meta properties**: Use dot notation directly (`entry.meta.title`)
- **Custom fields**: Access via `entry.fields['FIELD_NAME']` with **exact field name**

**CRITICAL: Field Name Matching**:
Field names **must match exactly** as defined in the Content Type, including:
- ✅ **Spaces**: `entry.fields['Icono SVG']` not `entry.fields['icono_svg']`
- ✅ **Accents**: `entry.fields['Título']` not `entry.fields['titulo']`
- ✅ **Capitalization**: `entry.fields['URL']` not `entry.fields['url']`

Use `type-get` tool to check exact field names before writing Liquid code.

---

### Filtering Entries

Modyo provides powerful filters for querying entries:

#### Filter by UUID
```liquid
{% assign entry = spaces['my-bank'].types['product'].entries | by_uuid: 'abc-123-def' %}
{{ entry.meta.title }}
```

#### Filter by Slug
```liquid
{% assign entry = spaces['my-bank'].types['product'].entries | by_slug: 'premium-account' %}
{{ entry.fields.interest_rate }}
```

#### Filter by Category
```liquid
{% assign news_entries = spaces['my-bank'].types['post'].entries | by_category: 'news' %}

{% for entry in news_entries %}
  <h2>{{ entry.meta.title }}</h2>
{% endfor %}
```

#### Filter by Tag
```liquid
{% assign campaign_entries = spaces['my-bank'].types['post'].entries | by_tag: 'campaign' %}
```

#### Filter by Language
```liquid
{% assign spanish_entries = spaces['my-bank'].types['post'].entries | by_lang: 'es' %}
```

#### Chaining Filters
```liquid
{% assign entries = spaces['my-bank'].types['post'].entries
  | by_category: 'news'
  | by_tag: 'campaign'
  | by_lang: 'en' %}
```

#### Custom Filtering (filter_by)
```liquid
{% assign featured = spaces['my-bank'].types['product'].entries
  | filter_by: 'fields.featured', true %}
```

---

### Sorting Entries

**Available sort fields**:
- `name` - Entry title
- `published_at` - Publication date
- `created_at` - Creation date
- `updated_at` - Last update date
- `slug` - Alphabetically by slug
- `fields.FIELD_UID` - Custom field value

**Syntax**:
```liquid
{% assign entries = spaces['my-bank'].types['product'].entries
  | sort_by: 'published_at', 'desc' %}

<!-- Sort by custom field -->
{% assign entries = spaces['my-bank'].types['product'].entries
  | sort_by: 'fields.priority', 'asc' %}
```

**Sort orders**: `'asc'` (ascending) or `'desc'` (descending)

---

### Pagination

**Basic Pagination**:
```liquid
{% assign entries = spaces['my-bank'].types['product'].entries
  | paginated: 10 %}  <!-- 10 entries per page -->

{% for entry in entries %}
  <div class="product">
    <h2>{{ entry.meta.title }}</h2>
  </div>
{% endfor %}

<!-- Pagination links -->
{{ entries | pagination_links }}
```

**Custom Pagination Links**:
```liquid
{% assign pagination = entries | pagination_links %}

<nav class="pagination">
  {% if pagination.prev %}
    <a href="{{ pagination.prev.url }}">Previous</a>
  {% endif %}

  {% for page in pagination.pages %}
    <a href="{{ page.url }}" {% if page.current %}class="active"{% endif %}>
      {{ page.number }}
    </a>
  {% endfor %}

  {% if pagination.next %}
    <a href="{{ pagination.next.url }}">Next</a>
  {% endif %}
</nav>
```

---

### Complete Examples

#### Example 1: Product Listing with Filters

```liquid
<!-- Widget: Product List -->
<!-- Access configured via widget variables -->
{% assign space_uid = vars.space_uid | default: 'my-bank' %}
{% assign type_uid = vars.type_uid | default: 'product' %}
{% assign category = vars.category | default: 'featured' %}

{% assign products = spaces[space_uid].types[type_uid].entries
  | by_category: category
  | sort_by: 'fields.priority', 'asc'
  | paginated: 12 %}

<div class="products-grid">
  {% for product in products %}
    <article class="product-card">
      <h3>{{ product.meta.title }}</h3>

      <!-- Custom fields -->
      <p class="description">{{ product.fields.description }}</p>
      <p class="price">${{ product.fields.price }}</p>

      <!-- Asset field -->
      {% if product.fields.image %}
        <img src="{{ product.fields.image.url }}"
             alt="{{ product.fields.image.description }}">
      {% endif %}

      <!-- Related content -->
      {% if product.fields.category %}
        <span class="category">{{ product.fields.category.name }}</span>
      {% endif %}

      <!-- Tags -->
      {% if product.meta.tags.size > 0 %}
        <div class="tags">
          {% for tag in product.meta.tags %}
            <span class="tag">{{ tag }}</span>
          {% endfor %}
        </div>
      {% endif %}

      <a href="/products/{{ product.meta.slug }}">View Details</a>
    </article>
  {% endfor %}
</div>

{{ products | pagination_links }}
```

#### Example 2: Blog Post with Related Entries

```liquid
<!-- Content Page: Entry Detail -->
<!-- entry object automatically available on entry pages -->

<article class="blog-post">
  <h1>{{ entry.meta.title }}</h1>

  <div class="meta">
    <time>{{ entry.meta.published_at | date: "%B %d, %Y" }}</time>
    <span>Category: {{ entry.meta.category.name }}</span>
  </div>

  <!-- Rich text field -->
  <div class="content">
    {{ entry.fields.body }}
  </div>

  <!-- Related posts by same category -->
  <section class="related-posts">
    <h2>Related Articles</h2>

    {% assign related = spaces['my-bank'].types['post'].entries
      | by_category: entry.meta.category.slug
      | sort_by: 'published_at', 'desc' %}

    <ul>
      {% for post in related limit: 3 %}
        {% unless post.meta.uuid == entry.meta.uuid %}
          <li>
            <a href="/blog/{{ post.meta.slug }}">{{ post.meta.title }}</a>
          </li>
        {% endunless %}
      {% endfor %}
    </ul>
  </section>
</article>
```

#### Example 3: Dynamic Navigation from Content

```liquid
<!-- Snippet: navigation_menu -->
<!-- Build navigation from content entries -->

{% assign nav_items = spaces['my-bank'].types['navigation-item'].entries
  | sort_by: 'fields.order', 'asc' %}

<nav class="main-nav">
  <ul>
    {% for item in nav_items %}
      <li>
        <a href="{{ item.fields.url }}"
           {% if item.fields.external %}target="_blank"{% endif %}>
          {{ item.meta.title }}
        </a>

        <!-- Nested navigation -->
        {% if item.fields.children.size > 0 %}
          <ul class="dropdown">
            {% for child in item.fields.children %}
              <li>
                <a href="{{ child.fields.url }}">{{ child.meta.title }}</a>
              </li>
            {% endfor %}
          </ul>
        {% endif %}
      </li>
    {% endfor %}
  </ul>
</nav>
```

#### Example 4: Location-Based Filtering with Maps

```liquid
<!-- Widget: Store Locator -->
{% assign locations = spaces['my-bank'].types['branch'].entries
  | sort_by: 'fields.name', 'asc' %}

<div class="store-locator">
  <div class="map">
    <!-- Dynamic map from location fields -->
    {{ locations | map: 'fields.location' | dynamic_map }}
  </div>

  <ul class="locations-list">
    {% for branch in locations %}
      <li>
        <h3>{{ branch.meta.title }}</h3>
        <p>{{ branch.fields.address }}</p>

        <!-- Location field with lat/lng -->
        {% if branch.fields.location %}
          <a href="https://maps.google.com/?q={{ branch.fields.location.lat }},{{ branch.fields.location.lng }}"
             target="_blank">
            Get Directions
          </a>
        {% endif %}
      </li>
    {% endfor %}
  </ul>
</div>
```

---

### Field Types in Liquid

Different content type fields are accessed differently:

| Field Type | Access Pattern | Example |
|-----------|---------------|---------|
| **Text/String** | `entry.fields.field_name` | `{{ entry.fields.title }}` |
| **Rich Text** | `entry.fields.field_name` | `{{ entry.fields.body }}` (renders HTML) |
| **Number** | `entry.fields.field_name` | `{{ entry.fields.price }}` |
| **Boolean** | `entry.fields.field_name` | `{% if entry.fields.featured %}` |
| **Date** | `entry.fields.field_name` | `{{ entry.fields.event_date \| date: "%Y-%m-%d" }}` |
| **Asset (single)** | `entry.fields.field_name.url` | `<img src="{{ entry.fields.image.url }}">` |
| **Assets (multiple)** | Loop through array | `{% for img in entry.fields.gallery %}` |
| **Location** | `entry.fields.field_name.lat/lng` | `{{ entry.fields.location.lat }}` |
| **Content Type (single)** | Nested entry object | `{{ entry.fields.author.meta.title }}` |
| **Content Types (multiple)** | Loop through entries | `{% for tag in entry.fields.tags %}` |

---

### Combining with Variables

**Use variables to make content queries dynamic**:

```liquid
<!-- Widget with configurable space/type -->
{% assign space_uid = vars.content_space | default: 'my-bank' %}
{% assign type_uid = vars.content_type | default: 'post' %}
{% assign limit = vars.entries_limit | default: 10 %}

{% assign entries = spaces[space_uid].types[type_uid].entries
  | sort_by: 'published_at', 'desc' %}

{% for entry in entries limit: limit %}
  <h2>{{ entry.meta.title }}</h2>
{% endfor %}
```

**Page-level overrides**:
```typescript
// Different content on different pages
page-add-widgets({
  pageId: 456,  // Homepage
  widgets: [{
    definition_uuid: "...",
    variables: [
      { slug: "content_space", value: "news" },
      { slug: "content_type", value: "featured-article" },
      { slug: "entries_limit", value: "5" }
    ]
  }]
})

page-add-widgets({
  pageId: 789,  // Blog page
  widgets: [{
    definition_uuid: "...",
    variables: [
      { slug: "content_space", value: "blog" },
      { slug: "content_type", value: "post" },
      { slug: "entries_limit", value: "20" }
    ]
  }]
})
```

---

### Best Practices

1. **Use UIDs, not IDs**: Content references use `space_uid` and `type_uid`, not numeric IDs
2. **Cache considerations**: Liquid rendering is server-side and cached; entries are fetched at render time
3. **Performance**: Use filters and limits to avoid fetching unnecessary entries
4. **Error handling**: Check for existence before accessing nested properties
5. **Pagination**: Always paginate large result sets
6. **Variables for flexibility**: Use widget variables to make content queries configurable

**Error handling example**:
```liquid
{% assign entry = spaces['my-bank'].types['product'].entries | by_slug: product_slug %}

{% if entry %}
  <h1>{{ entry.meta.title }}</h1>

  {% if entry.fields.image %}
    <img src="{{ entry.fields.image.url }}" alt="{{ entry.meta.title }}">
  {% endif %}
{% else %}
  <p>Product not found</p>
{% endif %}
```

---

### Integration with Content Tools

**Creating content that can be accessed in Channels**:

```typescript
// Step 1: Create space
space-create({
  platformSlug: "fed-team",
  name: "My Bank",
  uid: "my-bank"  // This UID is used in Liquid: spaces['my-bank']
})

// Step 2: Create content type
type-create({
  platformSlug: "fed-team",
  spaceId: 123,
  name: "Product",
  uid: "product"  // This UID is used in Liquid: types['product']
})

// Step 3: Create entry
entry-create({
  platformSlug: "fed-team",
  spaceId: 123,
  content_type_id: 456,
  name: "Premium Account",
  slug: "premium-account"  // Used for by_slug filter
})

// Step 4: Publish entry
entries-bulk-publish({
  platformSlug: "fed-team",
  spaceId: 123,
  entries: ["entry-uuid"]
})

// Step 5: Access in Channels
// spaces['my-bank'].types['product'].entries | by_slug: 'premium-account'
```

---

### Troubleshooting

**No entries returned**:
1. Check space UID and type UID are correct
2. Verify entries are published (not draft)
3. Check filters aren't excluding all entries
4. Verify content exists in correct space/type

**Field not accessible**:
1. Use `entry.fields['field_name']`, not `entry.field_name`
2. **CRITICAL**: Field name must match **exactly** (spaces, accents, capitalization)
3. Use `type-get` tool to check exact field names from Content Type schema
4. Examples:
   - ✅ Correct: `entry.fields['Título']` (with accent)
   - ✅ Correct: `entry.fields['Icono SVG']` (with space)
   - ❌ Wrong: `entry.fields['titulo']` (missing accent)
   - ❌ Wrong: `entry.fields['icono_svg']` (underscore instead of space)

**Pagination not working**:
1. Ensure `paginated` filter is applied: `| paginated: 10`
2. Check pagination links are rendered: `{{ entries | pagination_links }}`

---

## Related Documentation

- [MODYO_SITE_ARCHITECTURE.md](./MODYO_SITE_ARCHITECTURE.md) - Overall site structure
- [MODYO_PAGE_TYPES.md](./MODYO_PAGE_TYPES.md) - Page types that use snippets
- [tools/PAGE_WIDGET_TOOLS.md](./tools/PAGE_WIDGET_TOOLS.md) - Widget management
- [CONTEXT_FOR_NEW_TOOLS.md](./CONTEXT_FOR_NEW_TOOLS.md) - Tool development context
- [Modyo Liquid Objects Documentation](https://docs.modyo.com/en/platform/channels/liquid-markup/objects.html) - Complete reference

---

**Document Version**: 1.3.0
**Last Updated**: 2025-01-09
**Maintained By**: Modyo MCP Development Team

**Changelog**:
- **v1.3.0** (2025-01-09):
  - Added comprehensive "Accessing Content in Channels" section with Liquid markup examples
  - Documented how to use `spaces['uid'].types['uid'].entries` in all template contexts
  - Added filtering, sorting, pagination, and field type access patterns
  - Included 4 complete examples (product listing, blog posts, navigation, store locator)
  - Clarified system snippets are editable (not read-only), just not deletable
- **v1.2.0** (2025-01-09): Added comprehensive Variable System documentation with four-level hierarchy (Account → Site → Widget → Page), including page-level widget instance variables and stage isolation
- **v1.1.0** (2025-01-09): Added Liquid Drops/Objects documentation
- **v1.0.0** (2025-01-09): Initial release with snippet architecture

**Key Takeaways**:
1. **Snippets** are the rendering engine of Modyo. The `custom_widget` snippet implements the `sync` parameter behavior, and grid snippets determine column layouts.
2. **Liquid drops** provide access to platform, site, page, widget, and content data in all templates.
3. **Variables** have a clear four-level hierarchy: Page-level widget instance variables > Widget definition variables > Site variables (stage-isolated) > Account variables (global).
4. **Content access** via `spaces['uid'].types['uid'].entries` is available in all Liquid contexts: snippets, custom snippets, widgets, content pages, layouts, and templates.
5. Understanding snippets, Liquid objects, variables, and content access is essential for building Modyo sites.
