> ## Documentation Index
> Fetch the complete documentation index at: https://buttercms.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Repeaters: fetch and add to application

> Code examples for fetching and rendering data from Repeater fields in ButterCMS across multiple languages and frameworks.

# Fetching Repeater data via API

This guide provides comprehensive code examples for fetching and rendering Repeater data from ButterCMS across various programming languages and frameworks.

<Info>
  Need to set up repeaters first? See [Creating Repeater Fields](../../create-content/creating-editing-content/repeaters/creating-repeater-fields).
</Info>

## API response structure

When you publish a page with a Repeater field, the API returns your content as an array of items. Here are examples of common Repeater field responses:

<Tabs>
  <Tab title="Basic repeater">
    ```json theme={null}
    {
      "features": [
        {
          "title": "Fast Performance",
          "description": "Lightning quick load times",
          "icon": "https://cdn.buttercms.com/icon1.svg"
        },
        {
          "title": "Easy Integration",
          "description": "Works with any tech stack",
          "icon": "https://cdn.buttercms.com/icon2.svg"
        },
        {
          "title": "Great Support",
          "description": "24/7 expert assistance",
          "icon": "https://cdn.buttercms.com/icon3.svg"
        }
      ]
    }
    ```
  </Tab>

  <Tab title="Image gallery">
    ```json theme={null}
    {
      "gallery": [
        {
          "image": "https://cdn.buttercms.com/photo1.jpg",
          "caption": "Team meeting in progress",
          "alt_text": "Five team members collaborating around a table"
        },
        {
          "image": "https://cdn.buttercms.com/photo2.jpg",
          "caption": "Our modern office space",
          "alt_text": "Open office with natural lighting"
        }
      ]
    }
    ```
  </Tab>

  <Tab title="Testimonials">
    ```json theme={null}
    {
      "testimonials": [
        {
          "quote": "ButterCMS transformed our content workflow!",
          "author_name": "Jane Smith",
          "author_title": "Marketing Director",
          "author_company": "TechCorp",
          "author_photo": "https://cdn.buttercms.com/jane.jpg",
          "rating": 5
        },
        {
          "quote": "The best CMS we've ever used.",
          "author_name": "John Doe",
          "author_title": "CTO",
          "author_company": "StartupInc",
          "author_photo": "https://cdn.buttercms.com/john.jpg",
          "rating": 5
        }
      ]
    }
    ```
  </Tab>

  <Tab title="Image carousel">
    ```json theme={null}
    {
      "slug": "example-page",
      "page_type": null,
      "status": "published",
      "fields": {
        "headline": "Carousel Example",
        "image_carousel": [
          {
            "title": "Chicago",
            "caption": "Thank you, Chicago!",
            "image": "https://cdn.buttercms.com/WWANGqFQFeF1nWYOHaq7"
          },
          {
            "title": "New York",
            "caption": "Thanks New York!",
            "image": "https://cdn.buttercms.com/KrO9ueULT6OoEVX9lqxw"
          }
        ]
      }
    }
    ```
  </Tab>
</Tabs>

Each Repeater field returns as an array of items. As you add more items to a Repeater in the CMS, more objects appear in the array. A developer can map over that list and render the items in any way they want.

{/* SOURCE: file_path="knowledge-base/content-modeling-basics/buttercms-for-ecommerce.md" section="API response for developers" */}

## JavaScript / Node.js

### Using the ButterCMS JavaScript SDK

```javascript theme={null}
const butter = require('buttercms')('your-api-token');

async function getPageWithRepeaters() {
  try {
    const response = await butter.page.retrieve('*', 'landing-page');
    const page = response.data.data;

    // Access repeater data
    const features = page.fields.features;
    const testimonials = page.fields.testimonials;

    // Iterate over features
    features.forEach(feature => {
      console.log(`Feature: ${feature.title}`);
      console.log(`Description: ${feature.description}`);
      console.log(`Icon: ${feature.icon}`);
    });

    return page;
  } catch (error) {
    console.error('Error fetching page:', error);
  }
}
```

### Using Fetch API (Vanilla JavaScript)

```javascript theme={null}
const API_TOKEN = 'your-api-token';
const PAGE_SLUG = 'landing-page';

async function fetchRepeaterData() {
  const url = `https://api.buttercms.com/v2/pages/*/${PAGE_SLUG}/?auth_token=${API_TOKEN}`;

  const response = await fetch(url);
  const data = await response.json();

  const features = data.data.fields.features;

  // Render to DOM
  const container = document.getElementById('features-container');
  container.innerHTML = features.map(feature => `
    <div class="feature-card">
      <img src="${feature.icon}" alt="${feature.title}" />
      <h3>${feature.title}</h3>
      <p>${feature.description}</p>
    </div>
  `).join('');
}
```

## React

### React Component for Repeater data

```jsx theme={null}
import { useState, useEffect } from 'react';
import Butter from 'buttercms';

const butter = Butter('your-api-token');

function FeaturesSection() {
  const [features, setFeatures] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function fetchFeatures() {
      try {
        const response = await butter.page.retrieve('*', 'landing-page');
        setFeatures(response.data.data.fields.features);
      } catch (error) {
        console.error('Error:', error);
      } finally {
        setLoading(false);
      }
    }
    fetchFeatures();
  }, []);

  if (loading) return <div>Loading...</div>;

  return (
    <section className="features">
      <h2>Our Features</h2>
      <div className="features-grid">
        {features.map((feature, index) => (
          <div key={index} className="feature-card">
            <img src={feature.icon} alt="" className="feature-icon" />
            <h3>{feature.title}</h3>
            <p>{feature.description}</p>
          </div>
        ))}
      </div>
    </section>
  );
}

export default FeaturesSection;
```

### Image carousel Component

```jsx theme={null}
import { useState } from 'react';

function ImageCarousel({ images }) {
  const [currentIndex, setCurrentIndex] = useState(0);

  const goToNext = () => {
    setCurrentIndex((prev) => (prev + 1) % images.length);
  };

  const goToPrevious = () => {
    setCurrentIndex((prev) => (prev - 1 + images.length) % images.length);
  };

  return (
    <div className="carousel">
      <button onClick={goToPrevious} aria-label="Previous">←</button>

      <div className="carousel-slide">
        <img
          src={images[currentIndex].image}
          alt={images[currentIndex].alt_text || images[currentIndex].title}
        />
        <p className="caption">{images[currentIndex].caption}</p>
      </div>

      <button onClick={goToNext} aria-label="Next">→</button>

      <div className="carousel-dots">
        {images.map((_, index) => (
          <button
            key={index}
            className={index === currentIndex ? 'active' : ''}
            onClick={() => setCurrentIndex(index)}
            aria-label={`Go to slide ${index + 1}`}
          />
        ))}
      </div>
    </div>
  );
}
```

### Testimonials Component

```jsx theme={null}
function Testimonials({ testimonials }) {
  return (
    <section className="testimonials">
      <h2>What Our Customers Say</h2>
      <div className="testimonials-grid">
        {testimonials.map((testimonial, index) => (
          <div key={index} className="testimonial-card">
            <blockquote>"{testimonial.quote}"</blockquote>
            <div className="author">
              {testimonial.author_photo && (
                <img
                  src={testimonial.author_photo}
                  alt={testimonial.author_name}
                  className="author-photo"
                />
              )}
              <div className="author-info">
                <strong>{testimonial.author_name}</strong>
                <span>
                  {testimonial.author_title}, {testimonial.author_company}
                </span>
              </div>
            </div>
            {testimonial.rating && (
              <div className="rating">
                {'⭐'.repeat(testimonial.rating)}
              </div>
            )}
          </div>
        ))}
      </div>
    </section>
  );
}
```

## Vue.js

### Vue 3 composition API

```vue theme={null}
<template>
  <section v-if="!loading" class="features">
    <h2>Our Features</h2>
    <div class="features-grid">
      <div
        v-for="(feature, index) in features"
        :key="index"
        class="feature-card"
      >
        <img :src="feature.icon" :alt="feature.title" class="feature-icon" />
        <h3>{{ feature.title }}</h3>
        <p>{{ feature.description }}</p>
      </div>
    </div>
  </section>
  <div v-else>Loading...</div>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import Butter from 'buttercms';

const butter = Butter('your-api-token');
const features = ref([]);
const loading = ref(true);

onMounted(async () => {
  try {
    const response = await butter.page.retrieve('*', 'landing-page');
    features.value = response.data.data.fields.features;
  } catch (error) {
    console.error('Error:', error);
  } finally {
    loading.value = false;
  }
});
</script>
```

### FAQ accordion Component

```vue theme={null}
<template>
  <section class="faq">
    <h2>Frequently Asked Questions</h2>
    <div class="faq-list">
      <div
        v-for="(item, index) in faqItems"
        :key="index"
        class="faq-item"
      >
        <button
          class="faq-question"
          @click="toggleItem(index)"
          :aria-expanded="openItems.includes(index)"
        >
          {{ item.question }}
          <span class="icon">{{ openItems.includes(index) ? '−' : '+' }}</span>
        </button>
        <div v-show="openItems.includes(index)" class="faq-answer">
          <p>{{ item.answer }}</p>
        </div>
      </div>
    </div>
  </section>
</template>

<script setup>
import { ref } from 'vue';

const props = defineProps({
  faqItems: {
    type: Array,
    required: true
  }
});

const openItems = ref([]);

const toggleItem = (index) => {
  const itemIndex = openItems.value.indexOf(index);
  if (itemIndex > -1) {
    openItems.value.splice(itemIndex, 1);
  } else {
    openItems.value.push(index);
  }
};
</script>
```

### Testimonials Component

```vue theme={null}
<template>
  <section class="testimonials">
    <div
      v-for="(testimonial, index) in testimonials"
      :key="index"
      class="testimonial-card"
    >
      <blockquote>{{ testimonial.quote }}</blockquote>
      <div class="author">
        <img :src="testimonial.author_photo" :alt="testimonial.author_name" />
        <div>
          <strong>{{ testimonial.author_name }}</strong>
          <span>{{ testimonial.author_title }}, {{ testimonial.author_company }}</span>
        </div>
      </div>
      <div class="rating">
        <span v-for="n in testimonial.rating" :key="n">⭐</span>
      </div>
    </div>
  </section>
</template>

<script setup>
const props = defineProps({
  testimonials: Array
});
</script>
```

## Python

### Using the ButterCMS Python SDK

```python theme={null}
from butter_cms import ButterCMS

client = ButterCMS('your-api-token')

def get_page_with_repeaters():
    response = client.pages.get('*', 'landing-page')
    page = response['data']

    # Access repeater data
    features = page['fields']['features']

    for feature in features:
        print(f"Feature: {feature['title']}")
        print(f"Description: {feature['description']}")
        print(f"Icon: {feature['icon']}")
        print("---")

    return page
```

### Flask example

```python theme={null}
from flask import Flask, render_template
from butter_cms import ButterCMS

app = Flask(__name__)
client = ButterCMS('your-api-token')

@app.route('/features')
def features_page():
    response = client.pages.get('*', 'features-page')
    features = response['data']['fields']['features']
    return render_template('features.html', features=features)
```

```html theme={null}
<!-- templates/features.html -->
<section class="features">
  <h2>Our Features</h2>
  <div class="features-grid">
    {% for feature in features %}
    <div class="feature-card">
      <img src="{{ feature.icon }}" alt="{{ feature.title }}">
      <h3>{{ feature.title }}</h3>
      <p>{{ feature.description }}</p>
    </div>
    {% endfor %}
  </div>
</section>
```

### Django example

```python theme={null}
# views.py
from django.shortcuts import render
from butter_cms import ButterCMS

client = ButterCMS('your-api-token')

def landing_page(request):
    response = client.pages.get('*', 'landing-page')
    page = response['data']

    context = {
        'page_title': page['fields']['page_title'],
        'features': page['fields']['features'],
        'testimonials': page['fields']['testimonials'],
    }
    return render(request, 'landing_page.html', context)
```

```html theme={null}
<!-- templates/landing_page.html -->
<h1>{{ page_title }}</h1>

<section class="features">
  {% for feature in features %}
  <div class="feature-card">
    <img src="{{ feature.icon }}" alt="{{ feature.title }}">
    <h3>{{ feature.title }}</h3>
    <p>{{ feature.description }}</p>
  </div>
  {% endfor %}
</section>

<section class="testimonials">
  {% for testimonial in testimonials %}
  <blockquote>
    <p>"{{ testimonial.quote }}"</p>
    <cite>— {{ testimonial.author_name }}, {{ testimonial.author_company }}</cite>
  </blockquote>
  {% endfor %}
</section>
```

### Jinja2 template

```python theme={null}
# views.py
from butter_cms import ButterCMS

client = ButterCMS('your-api-token')
response = client.pages.get('*', 'landing-page')
page = response['data']['fields']
```

```html theme={null}
<!-- Jinja2 template -->
<section class="faq">
  {% for item in page.faq_items %}
  <div class="faq-item">
    <h3 class="question">{{ item.question }}</h3>
    <div class="answer">{{ item.answer }}</div>
  </div>
  {% endfor %}
</section>
```

## Go

### Processing features Repeater in Go

```go theme={null}
package main

import (
    "fmt"
    ButterCMS "github.com/ButterCMS/buttercms-go"
)

type Feature struct {
    Headline    string `json:"headline"`
    Description string `json:"description"`
}

func ProcessFeaturesSection(section map[string]interface{}) []Feature {
    unparsedFeatures, _ := section["features"].([]interface{})
    features := []Feature{}

    for _, unparsedFeature := range unparsedFeatures {
        typedFeature := unparsedFeature.(map[string]interface{})
        headline, _ := typedFeature["headline"].(string)
        description, _ := typedFeature["description"].(string)

        feature := Feature{
            Headline:    headline,
            Description: description,
        }
        features = append(features, feature)
    }
    return features
}
```

### Complete Go handler

```go theme={null}
package main

import (
    "html/template"
    "net/http"
    "os"

    ButterCMS "github.com/ButterCMS/buttercms-go"
)

type LandingPage struct {
    PageTitle string
    Features  []Feature
}

func landingPageHandler(w http.ResponseWriter, r *http.Request) {
    ButterCMS.SetAuthToken(os.Getenv("BUTTERCMS_API_TOKEN"))

    result, err := ButterCMS.GetPage("*", "landing-page", nil)
    if err != nil {
        http.Error(w, "Error fetching page", http.StatusInternalServerError)
        return
    }

    pageData := LandingPage{
        PageTitle: result.Page.Fields["page_title"].(string),
        Features:  ProcessFeaturesSection(result.Page.Fields),
    }

    tmpl := template.Must(template.ParseFiles("templates/landing.html"))
    tmpl.Execute(w, pageData)
}
```

## PHP / Laravel

### Using the ButterCMS PHP SDK

```php theme={null}
<?php
require_once 'vendor/autoload.php';

use ButterCMS\ButterCMS;

$butter = new ButterCMS('your-api-token');

$response = $butter->fetchPage('*', 'landing-page');
$page = $response->getData();

$features = $page['fields']['features'];

foreach ($features as $feature) {
    echo "<div class='feature-card'>";
    echo "<img src='{$feature['icon']}' alt='{$feature['title']}'>";
    echo "<h3>{$feature['title']}</h3>";
    echo "<p>{$feature['description']}</p>";
    echo "</div>";
}
```

### Laravel controller

```php theme={null}
<?php

namespace App\Http\Controllers;

use ButterCMS\ButterCMS;

class PageController extends Controller
{
    protected $butter;

    public function __construct()
    {
        $this->butter = new ButterCMS(env('BUTTERCMS_API_TOKEN'));
    }

    public function landing()
    {
        $response = $this->butter->fetchPage('*', 'landing-page');
        $page = $response->getData();

        return view('landing', [
            'pageTitle' => $page['fields']['page_title'],
            'features' => $page['fields']['features'],
            'testimonials' => $page['fields']['testimonials'],
        ]);
    }
}
```

### Laravel Blade template

```blade theme={null}
<!-- resources/views/landing.blade.php -->
<h1>{{ $pageTitle }}</h1>

<section class="features">
    @foreach ($features as $feature)
    <div class="feature-card">
        <img src="{{ $feature['icon'] }}" alt="{{ $feature['title'] }}">
        <h3>{{ $feature['title'] }}</h3>
        <p>{{ $feature['description'] }}</p>
    </div>
    @endforeach
</section>

<section class="testimonials">
    @foreach ($testimonials as $testimonial)
    <blockquote>
        <p>"{{ $testimonial['quote'] }}"</p>
        <cite>— {{ $testimonial['author_name'] }}, {{ $testimonial['author_company'] }}</cite>
    </blockquote>
    @endforeach
</section>
```

## Ruby / Rails

### Using the ButterCMS Ruby SDK

```ruby theme={null}
require 'buttercms-ruby'

ButterCMS::api_token = 'your-api-token'

page = ButterCMS::Page.find('*', 'landing-page')
features = page.fields[:features]

features.each do |feature|
  puts "Feature: #{feature[:title]}"
  puts "Description: #{feature[:description]}"
  puts "---"
end
```

### Rails controller

```ruby theme={null}
# app/controllers/pages_controller.rb
class PagesController < ApplicationController
  def landing
    page = ButterCMS::Page.find('*', 'landing-page')
    @page_title = page.fields[:page_title]
    @features = page.fields[:features]
    @testimonials = page.fields[:testimonials]
  end
end
```

### Rails view (ERB)

```erb theme={null}
<!-- app/views/pages/landing.html.erb -->
<h1><%= @page_title %></h1>

<section class="features">
  <% @features.each do |feature| %>
  <div class="feature-card">
    <img src="<%= feature[:icon] %>" alt="<%= feature[:title] %>">
    <h3><%= feature[:title] %></h3>
    <p><%= feature[:description] %></p>
  </div>
  <% end %>
</section>

<section class="testimonials">
  <% @testimonials.each do |testimonial| %>
  <blockquote>
    <p>"<%= testimonial[:quote] %>"</p>
    <cite>— <%= testimonial[:author_name] %>, <%= testimonial[:author_company] %></cite>
  </blockquote>
  <% end %>
</section>
```

## Repeater structure

Repeaters are single-level lists in this codebase. Repeater subfields can only be standard field types.

## Error handling best practices

```javascript theme={null}
async function fetchPageSafely(pageSlug) {
  try {
    const response = await butter.page.retrieve('*', pageSlug);

    // Check if repeater exists and has items
    const features = response.data?.data?.fields?.features;

    if (!features || !Array.isArray(features)) {
      console.warn('Features repeater is empty or missing');
      return [];
    }

    return features;
  } catch (error) {
    if (error.response?.status === 404) {
      console.error('Page not found');
    } else if (error.response?.status === 401) {
      console.error('Invalid API token');
    } else {
      console.error('Unexpected error:', error);
    }
    return [];
  }
}
```
