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 REELScolumns- 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
- Go to Appearance → Widgets in WordPress admin
- Drag "Instagram Feed (Feedframer)" to your sidebar
- Configure title, post count, and type
- 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
- Use Transients - Cache API responses for at least 1 hour
- Error Handling - Always provide fallback content
- Secure API Key - Store in
wp-config.php, not the database - Optimize Images - Consider using WordPress image optimization plugins
- Lazy Loading - Use
loading="lazy"on images - 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';