Building an Instagram Feed Module in Drupal 10
Building an Instagram Feed Module in Drupal 10
So, you want to show your Instagram posts on your Drupal site? Yeah, I've had this request a bunch of times from clients. They want to display their Instagram feed right on their website without paying for some expensive third-party service.
Here's how I built my own module.
What You'll Need
- Drupal 10 up and running
- Some PHP knowledge (not too much, I'll walk you through it)
- An Instagram account (to get the API token)
Getting the Instagram Token
Before we write any code, we need to get access to the Instagram API:
- Go to developers.facebook.com
- Create a new app
- Add "Instagram Basic Display" to your app
- Add yourself as a Test User
- Generate a long-lived access token (they expire, so this is important)
Save that token somewhere - you'll need it later.
Set Up the Module Structure
Create a folder in your Drupal installation:
modules/custom/instagram_feed/
├── instagram_feed.info.yml
├── instagram_feed.module
├── instagram_feed.routing.yml
├── src/
│ └── Controller/
│ └── InstagramFeedController.php
├── templates/
│ └── instagram-feed.html.twig
└── instagram_feed.libraries.yml
Step 1: The Info File
# instagram_feed.info.yml
name: Instagram Feed
description: Displays an Instagram feed on your website
core_version_requirement: ^10
package: Custom
version: 1.0.0
dependencies:
- drupal:block
Step 2: The Module File
// instagram_feed.module
<?php
use Drupal\Core\Routing\RouteMatchInterface;
function instagram_feed_help($route_name, RouteMatchInterface $route_match) {
if ($route_name == 'help.page.instagram_feed') {
return '<p>Display Instagram feed on your website.</p>';
}
}
function instagram_feed_theme() {
return [
'instagram_feed' => [
'template' => 'instagram-feed',
'variables' => [
'items' => [],
'access_token' => '',
],
],
];
}
Step 3: Routing
# instagram_feed.routing.yml
instagram_feed.admin_settings:
path: '/admin/config/instagram-feed'
defaults:
_title: 'Instagram Feed Settings'
_form: '\Drupal\instagram_feed\Form\InstagramFeedSettingsForm'
requirements:
_permission: 'administer site configuration'
Step 4: The Controller
This is where the magic happens - fetching the Instagram posts:
// src/Controller/InstagramFeedController.php
<?php
namespace Drupal\instagram_feed\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Config\ConfigFactoryInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use GuzzleHttp\Client;
class InstagramFeedController extends ControllerBase {
protected $httpClient;
protected $configFactory;
public function __construct(Client $http_client, ConfigFactoryInterface $config_factory) {
$this->httpClient = $http_client;
$this->configFactory = $config_factory;
}
public static function create(ContainerInterface $container) {
return new static(
$container->get('http_client'),
$container->get('config.factory')
);
}
public function getFeed() {
$config = $this->configFactory->get('instagram_feed.settings');
$access_token = $config->get('access_token');
$limit = $config->get('limit') ?? 9;
$items = [];
if ($access_token) {
try {
$url = "https://graph.instagram.com/me/media?fields=id,caption,media_type,media_url,thumbnail_url,permalink&access_token={$access_token}&limit={$limit}";
$response = $this->httpClient->get($url);
$data = json_decode($response->getBody()->getContents(), TRUE);
if (isset($data['data'])) {
foreach ($data['data'] as $item) {
$items[] = [
'id' => $item['id'],
'url' => $item['media_type'] === 'VIDEO'
? ($item['thumbnail_url'] ?? $item['media_url'])
: $item['media_url'],
'link' => $item['permalink'],
'caption' => $item['caption'] ?? '',
'type' => $item['media_type'],
];
}
}
}
catch (\Exception $e) {
\Drupal::logger('instagram_feed')->error('Failed to fetch Instagram feed: @message', [
'@message' => $e->getMessage(),
]);
}
}
return [
'#theme' => 'instagram_feed',
'#items' => $items,
'#cache' => [
'tags' => ['instagram_feed_cache'],
'max-age' => 3600,
],
];
}
}
Step 5: Settings Form
Let admins enter their token without touching code:
// src/Form/InstagramFeedSettingsForm.php
<?php
namespace Drupal\instagram_feed\Form;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
class InstagramFeedSettingsForm extends ConfigFormBase {
protected function getEditableConfigNames() {
return ['instagram_feed.settings'];
}
public function getFormId() {
return 'instagram_feed_settings_form';
}
public function buildForm(array $form, FormStateInterface $form_state) {
$config = $config = $this->config('instagram_feed.settings');
$form['access_token'] = [
'#type' => 'textfield',
'#title' => $this->t('Instagram Access Token'),
'#description' => $this->t('Enter your Instagram Basic Display API access token.'),
'#default_value' => $config->get('access_token'),
'#required' => TRUE,
];
$form['limit'] = [
'#type' => 'number',
'#title' => $this->t('Number of items'),
'#default_value' => $config->get('limit') ?? 9,
'#min' => 1,
'#max' => 25,
];
return parent::buildForm($form, $form_state);
}
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->config('instagram_feed.settings')
->set('access_token', $form_state->getValue('access_token'))
->set('limit', $form_state->getValue('limit'))
->save();
parent::submitForm($form, $form_state);
}
}
Step 6: The Template
{# templates/instagram-feed.html.twig #}
<div class="instagram-feed">
{% if items %}
<div class="instagram-feed-grid">
{% for item in items %}
<a href="{{ item.link }}" target="_blank" class="instagram-feed-item">
<img src="{{ item.url }}" alt="{{ item.caption }}" loading="lazy" />
</a>
{% endfor %}
</div>
{% else %}
<p>No Instagram posts to display.</p>
{% endif %}
</div>
<style>
.instagram-feed-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
}
.instagram-feed-item img {
width: 100%;
height: auto;
display: block;
}
</style>
Step 7: Enable and Configure
-
Enable it:
drush en instagram_feed -
Go to
/admin/config/instagram-feed -
Paste your access token
-
Save
-
Add the block to a region or embed in a template
Why Build Your Own?
I know what you're thinking - "why not just use a module from Drupal.org?"
Here's the thing:
- Most free modules have limitations
- Paid modules are expensive
- Building your own means you control everything
- It's actually not that hard once you see the code
Wrapping Up
That's it! You've got a working Instagram feed module. You can style it however you want, add caching, build a better UI - whatever the client needs.
The Instagram API changes sometimes, so you might need to update the endpoint URLs eventually. But the basic structure should work for a while.
Need help building custom Drupal modules? That's what I do. Let's talk.