WordPress Integration

Introduction

This guide shows you how to integrate Feedframer into your WordPress site to display Instagram posts using shortcodes, widgets, and custom templates.

Setup

Step 1: Add API Key to wp-config.php

Add your Feedframer API key to wp-config.php:

// wp-config.php

define('FEEDFRAMER_API_KEY', 'your_api_key_here');

This keeps your API key secure and out of the database.

Step 2: Create Helper Function

Add this function to your theme's functions.php:

// functions.php

function feedframer_get_posts($args = []) {
    $api_key = FEEDFRAMER_API_KEY;

    $defaults = [
        'api_key' => $api_key,
        'page[size]' => 12,
    ];

    $params = array_merge($defaults, $args);
    $url = 'https://feedframer.com/api/v1/me?' . http_build_query($params);

    // Check transient cache first
    $cache_key = 'feedframer_' . md5($url);
    $cached = get_transient($cache_key);

    if ($cached !== false) {
        return $cached;
    }

    // Fetch from API
    $response = wp_remote_get($url);

    if (is_wp_error($response)) {
        error_log('Feedframer API Error: ' . $response->get_error_message());
        return [];
    }

    $body = wp_remote_retrieve_body($response);
    $data = json_decode($body, true);

    if (!isset($data['posts'])) {
        return [];
    }

    // Cache for 1 hour
    set_transient($cache_key, $data['posts'], HOUR_IN_SECONDS);

    return $data['posts'];
}

Shortcode Implementation

Create a shortcode to display Instagram posts anywhere:

// functions.php

function feedframer_shortcode($atts) {
    $atts = shortcode_atts([
        'limit' => 12,
        'type' => '',
        'columns' => 3,
    ], $atts);

    $args = [
        'page[size]' => intval($atts['limit']),
    ];

    if (!empty($atts['type'])) {
        $args['filter[type]'] = strtoupper($atts['type']);
    }

    $posts = feedframer_get_posts($args);

    if (empty($posts)) {
        return '<p>No Instagram posts found.</p>';
    }

    ob_start();
    ?>

    <div class="feedframer-grid" style="display: grid; grid-template-columns: repeat(<?php echo intval($atts['columns']); ?>, 1fr); gap: 1rem;">
        <?php foreach ($posts as $post): ?>
            <div class="feedframer-post">
                <a href="<?php echo esc_url($post['permalink']); ?>" target="_blank" rel="noopener">
                    <img
                        src="<?php echo esc_url($post['mediaUrl']); ?>"
                        alt="<?php echo esc_attr(wp_trim_words($post['caption'], 10)); ?>"
                        style="width: 100%; height: auto; display: block; border-radius: 8px;"
                    />
                </a>

                <?php if (!empty($post['caption'])): ?>
                    <p style="margin-top: 0.5rem; font-size: 14px; color: #666;">
                        <?php echo esc_html(wp_trim_words($post['caption'], 20)); ?>
                    </p>
                <?php endif; ?>
            </div>
        <?php endforeach; ?>
    </div>

    <?php
    return ob_get_clean();
}

add_shortcode('feedframer', 'feedframer_shortcode');

Shortcode Usage

Use the shortcode in posts, pages, or widgets:

[feedframer limit="12" type="IMAGE" columns="3"]

Parameters:

  • limit - Number of posts (default: 12)
  • type - Filter by type: IMAGE, VIDEO, CAROUSEL_ALBUM, or REELS
  • columns - Grid columns (default: 3)

Widget Implementation

Create a WordPress widget for the sidebar:

// functions.php

class Feedframer_Widget extends WP_Widget {
    public function __construct() {
        parent::__construct(
            'feedframer_widget',
            'Instagram Feed (Feedframer)',
            ['description' => 'Display your Instagram posts from Feedframer']
        );
    }

    public function widget($args, $instance) {
        echo $args['before_widget'];

        if (!empty($instance['title'])) {
            echo $args['before_title'] . esc_html($instance['title']) . $args['after_title'];
        }

        $limit = !empty($instance['limit']) ? intval($instance['limit']) : 6;
        $type = !empty($instance['type']) ? $instance['type'] : '';

        $feed_args = [
            'page[size]' => $limit,
        ];

        if ($type) {
            $feed_args['filter[type]'] = $type;
        }

        $posts = feedframer_get_posts($feed_args);

        if (!empty($posts)): ?>
            <div class="feedframer-widget-grid" style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.5rem;">
                <?php foreach ($posts as $post): ?>
                    <a href="<?php echo esc_url($post['permalink']); ?>" target="_blank">
                        <img
                            src="<?php echo esc_url($post['mediaUrl']); ?>"
                            alt="Instagram post"
                            style="width: 100%; height: auto; border-radius: 4px;"
                        />
                    </a>
                <?php endforeach; ?>
            </div>
        <?php else: ?>
            <p>No posts found.</p>
        <?php endif;

        echo $args['after_widget'];
    }

    public function form($instance) {
        $title = !empty($instance['title']) ? $instance['title'] : 'Instagram Feed';
        $limit = !empty($instance['limit']) ? $instance['limit'] : 6;
        $type = !empty($instance['type']) ? $instance['type'] : '';
        ?>

        <p>
            <label for="<?php echo $this->get_field_id('title'); ?>">Title:</label>
            <input
                class="widefat"
                id="<?php echo $this->get_field_id('title'); ?>"
                name="<?php echo $this->get_field_name('title'); ?>"
                type="text"
                value="<?php echo esc_attr($title); ?>"
            />
        </p>

        <p>
            <label for="<?php echo $this->get_field_id('limit'); ?>">Number of posts:</label>
            <input
                class="tiny-text"
                id="<?php echo $this->get_field_id('limit'); ?>"
                name="<?php echo $this->get_field_name('limit'); ?>"
                type="number"
                min="1"
                max="20"
                value="<?php echo esc_attr($limit); ?>"
            />
        </p>

        <p>
            <label for="<?php echo $this->get_field_id('type'); ?>">Post Type:</label>
            <select
                class="widefat"
                id="<?php echo $this->get_field_id('type'); ?>"
                name="<?php echo $this->get_field_name('type'); ?>"
            >
                <option value="" <?php selected($type, ''); ?>>All Types</option>
                <option value="IMAGE" <?php selected($type, 'IMAGE'); ?>>Images</option>
                <option value="VIDEO" <?php selected($type, 'VIDEO'); ?>>Videos</option>
                <option value="REELS" <?php selected($type, 'REELS'); ?>>Reels</option>
            </select>
        </p>
        <?php
    }

    public function update($new_instance, $old_instance) {
        $instance = [];
        $instance['title'] = sanitize_text_field($new_instance['title']);
        $instance['limit'] = intval($new_instance['limit']);
        $instance['type'] = sanitize_text_field($new_instance['type']);

        // Clear cache when widget is updated
        delete_transient('feedframer_*');

        return $instance;
    }
}

function register_feedframer_widget() {
    register_widget('Feedframer_Widget');
}
add_action('widgets_init', 'register_feedframer_widget');

Using the Widget

  1. Go to Appearance → Widgets in WordPress admin
  2. Drag "Instagram Feed (Feedframer)" to your sidebar
  3. Configure title, post count, and type
  4. Save

Template Tag

Use in theme template files:

// functions.php

function feedframer_display_feed($args = []) {
    $posts = feedframer_get_posts($args);

    if (empty($posts)) {
        echo '<p>No Instagram posts found.</p>';
        return;
    }
    ?>

    <div class="instagram-feed">
        <?php foreach ($posts as $post): ?>
            <div class="instagram-post">
                <a href="<?php echo esc_url($post['permalink']); ?>" target="_blank">
                    <img src="<?php echo esc_url($post['mediaUrl']); ?>" alt="" />
                </a>
            </div>
        <?php endforeach; ?>
    </div>
    <?php
}

Usage in theme files:

// footer.php or any template file

<?php feedframer_display_feed(['page[size]' => 6, 'filter[type]' => 'IMAGE']); ?>

Gutenberg Block

Create a Gutenberg block:

// functions.php

function feedframer_register_block() {
    if (!function_exists('register_block_type')) {
        return;
    }

    wp_register_script(
        'feedframer-block',
        get_template_directory_uri() . '/blocks/feedframer.js',
        ['wp-blocks', 'wp-element', 'wp-editor'],
        filemtime(get_template_directory() . '/blocks/feedframer.js')
    );

    register_block_type('feedframer/instagram-feed', [
        'editor_script' => 'feedframer-block',
        'render_callback' => 'feedframer_block_render',
        'attributes' => [
            'limit' => [
                'type' => 'number',
                'default' => 12,
            ],
            'type' => [
                'type' => 'string',
                'default' => '',
            ],
        ],
    ]);
}
add_action('init', 'feedframer_register_block');

function feedframer_block_render($attributes) {
    return feedframer_shortcode($attributes);
}

Caching Strategy

Implement multi-layer caching:

// functions.php

function feedframer_get_posts_with_cache($args = []) {
    $api_key = FEEDFRAMER_API_KEY;

    $defaults = [
        'api_key' => $api_key,
        'page[size]' => 12,
    ];

    $params = array_merge($defaults, $args);
    $cache_key = 'feedframer_' . md5(serialize($params));

    // Try object cache (Redis/Memcached)
    $cached = wp_cache_get($cache_key, 'feedframer');
    if ($cached !== false) {
        return $cached;
    }

    // Try transient cache
    $cached = get_transient($cache_key);
    if ($cached !== false) {
        wp_cache_set($cache_key, $cached, 'feedframer', HOUR_IN_SECONDS);
        return $cached;
    }

    // Fetch from API
    $url = 'https://feedframer.com/api/v1/me?' . http_build_query($params);
    $response = wp_remote_get($url, ['timeout' => 10]);

    if (is_wp_error($response)) {
        error_log('Feedframer: ' . $response->get_error_message());
        return [];
    }

    $body = wp_remote_retrieve_body($response);
    $data = json_decode($body, true);

    if (!isset($data['posts'])) {
        return [];
    }

    $posts = $data['posts'];

    // Store in both caches
    wp_cache_set($cache_key, $posts, 'feedframer', HOUR_IN_SECONDS);
    set_transient($cache_key, $posts, HOUR_IN_SECONDS);

    return $posts;
}

Clear Cache Function

// functions.php

function feedframer_clear_cache() {
    global $wpdb;

    // Delete all Feedframer transients
    $wpdb->query(
        "DELETE FROM {$wpdb->options}
         WHERE option_name LIKE '_transient_feedframer_%'
            OR option_name LIKE '_transient_timeout_feedframer_%'"
    );

    // Flush object cache
    wp_cache_flush();
}

// Add admin menu item to clear cache
function feedframer_admin_menu() {
    add_submenu_page(
        'tools.php',
        'Clear Feedframer Cache',
        'Clear Feedframer Cache',
        'manage_options',
        'feedframer-clear-cache',
        'feedframer_admin_page'
    );
}
add_action('admin_menu', 'feedframer_admin_menu');

function feedframer_admin_page() {
    if (isset($_POST['clear_cache'])) {
        feedframer_clear_cache();
        echo '<div class="notice notice-success"><p>Cache cleared successfully!</p></div>';
    }
    ?>

    <div class="wrap">
        <h1>Feedframer Settings</h1>

        <form method="post">
            <p>Clear the Instagram post cache to fetch fresh data from Feedframer.</p>
            <button type="submit" name="clear_cache" class="button button-primary">
                Clear Cache
            </button>
        </form>
    </div>
    <?php
}

Filtering

Filter by Type

// Images only
$images = feedframer_get_posts(['filter[type]' => 'IMAGE']);

// Videos only
$videos = feedframer_get_posts(['filter[type]' => 'VIDEO']);

// Reels only
$reels = feedframer_get_posts(['filter[type]' => 'REELS']);

Filter by Date Range

$recent_posts = feedframer_get_posts([
    'filter[publishedAfter]' => date('Y-m-d', strtotime('-30 days')),
    'page[size]' => 50,
]);

Error Handling

Graceful error handling with fallbacks:

function feedframer_get_posts_safe($args = []) {
    $posts = feedframer_get_posts($args);

    if (empty($posts)) {
        // Try to get last successful cache
        $fallback = get_option('feedframer_last_successful_fetch', []);

        if (!empty($fallback)) {
            error_log('Feedframer: Using fallback cache');
            return $fallback;
        }

        return [];
    }

    // Store successful fetch as fallback
    update_option('feedframer_last_successful_fetch', $posts);

    return $posts;
}

Custom Post Type Integration

Store Instagram posts as WordPress posts:

// Scheduled event to import posts
function feedframer_schedule_import() {
    if (!wp_next_scheduled('feedframer_import_posts')) {
        wp_schedule_event(time(), 'hourly', 'feedframer_import_posts');
    }
}
add_action('wp', 'feedframer_schedule_import');

function feedframer_import_posts_to_cpt() {
    $posts = feedframer_get_posts(['page[size]' => 50]);

    foreach ($posts as $post) {
        $instagram_id = $post['id'];

        // Check if post already exists
        $existing = get_posts([
            'post_type' => 'instagram_post',
            'meta_key' => 'instagram_id',
            'meta_value' => $instagram_id,
            'posts_per_page' => 1,
        ]);

        if (!empty($existing)) {
            continue;
        }

        // Create new post
        $post_id = wp_insert_post([
            'post_type' => 'instagram_post',
            'post_title' => wp_trim_words($post['caption'], 10),
            'post_content' => $post['caption'],
            'post_status' => 'publish',
            'meta_input' => [
                'instagram_id' => $instagram_id,
                'media_url' => $post['mediaUrl'],
                'permalink' => $post['permalink'],
            ],
        ]);

        // Download and set featured image
        if ($post_id) {
            feedframer_set_featured_image($post_id, $post['mediaUrl']);
        }
    }
}
add_action('feedframer_import_posts', 'feedframer_import_posts_to_cpt');

function feedframer_set_featured_image($post_id, $image_url) {
    require_once(ABSPATH . 'wp-admin/includes/media.php');
    require_once(ABSPATH . 'wp-admin/includes/file.php');
    require_once(ABSPATH . 'wp-admin/includes/image.php');

    $attachment_id = media_sideload_image($image_url, $post_id, '', 'id');

    if (!is_wp_error($attachment_id)) {
        set_post_thumbnail($post_id, $attachment_id);
    }
}

REST API Endpoint

Create a custom WordPress REST API endpoint:

// functions.php

function feedframer_register_rest_route() {
    register_rest_route('feedframer/v1', '/posts', [
        'methods' => 'GET',
        'callback' => 'feedframer_rest_get_posts',
        'permission_callback' => '__return_true',
    ]);
}
add_action('rest_api_init', 'feedframer_register_rest_route');

function feedframer_rest_get_posts(WP_REST_Request $request) {
    $type = $request->get_param('type');
    $limit = $request->get_param('limit') ?: 12;

    $args = [
        'page[size]' => intval($limit),
    ];

    if ($type) {
        $args['filter[type]'] = $type;
    }

    $posts = feedframer_get_posts($args);

    return rest_ensure_response($posts);
}

Usage:

GET /wp-json/feedframer/v1/posts?type=IMAGE&limit=12

Best Practices

  1. Use Transients - Cache API responses for at least 1 hour
  2. Error Handling - Always provide fallback content
  3. Secure API Key - Store in wp-config.php, not the database
  4. Optimize Images - Consider using WordPress image optimization plugins
  5. Lazy Loading - Use loading="lazy" on images
  6. Clear Cache - Provide admin option to clear cache

Complete Plugin Example

Create a simple plugin:

<?php
/**
 * Plugin Name: Feedframer Instagram Feed
 * Description: Display Instagram posts from Feedframer
 * Version: 1.0.0
 * Author: Your Name
 */

if (!defined('ABSPATH')) {
    exit;
}

// Include API key from wp-config.php
if (!defined('FEEDFRAMER_API_KEY')) {
    add_action('admin_notices', function() {
        echo '<div class="notice notice-error"><p>Feedframer: Please add FEEDFRAMER_API_KEY to wp-config.php</p></div>';
    });
    return;
}

// Helper function
require_once plugin_dir_path(__FILE__) . 'includes/functions.php';

// Shortcode
require_once plugin_dir_path(__FILE__) . 'includes/shortcode.php';

// Widget
require_once plugin_dir_path(__FILE__) . 'includes/widget.php';

// Admin
require_once plugin_dir_path(__FILE__) . 'includes/admin.php';

Next Steps