WP Manifestindependent plugin directory
manifest / media / curated-instagram-wall

Curated Instagram Wall

A self-hosted, curated Instagram-style grid for WordPress. Images live in the media library; no API, no oEmbed, no hotlinking.

by Kushagra Bindal · github.com/kushagra-bindal/curated-instagram-wall · website

0stars
0forks

Install

No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:

wp plugin install https://github.com/kushagra-bindal/curated-instagram-wall/archive/refs/heads/main.zip

Readme

Curated Instagram Wall

A self-hosted WordPress plugin that shows a curated Instagram-style feed as a clean profile grid.

The core principle

Every image is uploaded into the WordPress media library and served from the same server as the site. There is no Instagram API, no access token, no oEmbed, no embedded iframe and no hotlinked CDN URL anywhere. The expiring-URL problem that turns other Instagram feeds into black tiles simply cannot happen: all content is local, so it always loads.

The grid mimics an Instagram profile grid: 3:4 portrait media (Instagram's current grid ratio), tight gaps, a centred play triangle on reels, a stacked-squares badge on carousels, and no like/comment/share UI at all. Above the grid sits a small avatar + name; below it, a centred Load More + Follow on Instagram button pair.

Screenshots

Grid Lightbox
Front-end grid Lightbox
Card editor Settings
Card editor Settings screen

Installation

  1. Download the latest release zip, or clone this repository into wp-content/plugins/curated-instagram-wall.
  2. Plugins → Add New → Upload Plugin, choose the zip, Install Now, Activate.
  3. A new Instagram Wall menu appears in the sidebar.

Requires WordPress 6.0+ and PHP 7.4+.

Adding a card

Go to Instagram Wall → Add New. The same steps are shown on that screen. Only the fields for the chosen media type appear.

  1. Title: internal label only, e.g. "Summer workshop 2026 – reel". Visitors never see it.
  2. Instagram post URL: on Instagram open the post → ShareCopy link, paste it in. Required; it powers the "View on Instagram" links.
  3. Media type: the upload fields switch automatically.
    • Photo: upload one image.
    • Carousel: upload all the slides, in order (drag the thumbnails to reorder; the first slide is the card image). The card becomes a swipeable slider.
    • Reel: upload a thumbnail image (screenshot the reel cover; required), and optionally the MP4 video. With a video the reel plays in a lightbox on the page; without one, clicking opens Instagram.
  4. Caption: plain text, shown Instagram-style with a "more" toggle.
  5. Order (Page Attributes): lower numbers appear first. Then Publish.

Publishing is blocked (saved as draft, with a notice saying exactly what is missing) until the card has its link and its picture(s). Incomplete cards are flagged in the list table and never render on the front end.

What happens when a visitor clicks a card

Card Click result
Photo Lightbox with the large image + "View on Instagram" link
Carousel Swipeable on the card itself (arrows/dots/touch); click opens a lightbox with the full swipeable set + link
Reel with MP4 Opens a centred lightbox and starts playing immediately (native controls + link)
Reel without MP4 Opens the Instagram post in a new tab

Who can add cards

Cards use the standard post capabilities, so Authors, Editors and Contributors manage them with the accounts they already have. Only the Settings page is Administrator-only (manage_options).

Settings

Instagram Wall → Settings (Administrators only):

Section Setting Default Notes
Grid layout Desktop / Tablet / Mobile columns 5 / 3 / 2 Breakpoints at 1024px and 600px; values 1–12
Grid layout Captions Hidden Or Below the picture (with a "more" toggle), or On hover (overlay, always visible on touch)
Profile Show header On Avatar + name above the grid; skipped entirely when nothing is filled in
Profile Avatar Any media library image; square works best
Profile Display name Bold text next to the avatar; falls back to @handle
Profile Instagram handle Without the @
Profile Profile URL Target of the Follow on Instagram button; leave empty to hide it
Load more Posts shown at first 10 Tiles visible before the first click (1–100)
Load more Posts per click 10 Tiles revealed per click (1–100)

Shortcode

Add the shortcode to any page (Shortcode block in the block editor):

[ciw_wall]

[ig_feed] is registered as an alias and behaves identically:

[ig_feed]

Attributes

Anything in the shortcode beats the Settings page:

[ciw_wall desktop="4" tablet="2" mobile="1" limit="12" caption="hover" header="no" initial="8" batch="4"]
Attribute Values Meaning
desktop / tablet / mobile 1–12 Columns at each screen size
limit integer Maximum number of cards; 0 or omitted shows all
caption below, hover, off Caption mode (also accepts yes/no)
header yes, no Show the profile header
initial 1–100 Cards visible before Load More
batch 1–100 Cards revealed per Load More click

The same attributes work on [ig_feed]. Cards are ordered by the Order field (ascending), then newest first.

How it works (for developers)

  • Prefixes: ciw_ / CIW_. Text domain: curated-instagram-wall.
  • Post type ciw_post: capability_type => 'post', map_meta_cap => true, public => false, show_ui => true. Classic editor on purpose (show_in_rest => false): the publish-blocking validation and its notices need the synchronous post.php save flow.
  • Meta keys: _ciw_caption, _ciw_url, _ciw_media_type (photo / reel / carousel), _ciw_image_id (photo image / reel thumbnail), _ciw_gallery_ids (ordered array of carousel slide attachment IDs), _ciw_video_id. All registered via register_post_meta() with sanitize and auth callbacks.
  • Option: ciw_settings (a single array, sanitised as a whole by CIW_Settings::sanitize()), registered in the ciw_settings_group settings group. Settings page slug: ciw-settings.
  • Per-type renderability (ciw_card_is_renderable()): a valid instagram.com URL and the required local media: photo needs an image, reel needs a thumbnail image, carousel needs at least one slide. This one function gates both publishing and front-end output.
  • Publish validator: CIW_Meta::validate() runs on save_post_ciw_post at priority 20, after the meta box save at priority 10, on every save path (form, quick edit, programmatic). A publish or future card that is not renderable is demoted to draft, and a ciw_blocked query arg drives the admin notice.
  • Attachment validation: every attachment ID is checked (get_post_type() === 'attachment', wp_attachment_is_image() for images) on save and on read.
  • Asset loading: front-end CSS/JS (handle ciw-feed) are registered on wp_enqueue_scripts and enqueued only when the shortcode actually renders. Admin CSS/JS (handle ciw-admin) load only on the plugin's own screens; the media modal and jQuery UI sortable are enqueued only on the editor and settings screens, never on the list table.
  • JavaScript: the front end is vanilla JS with no jQuery. One small slider engine powers both card carousels and the lightbox carousel (arrows, dots, pointer-event touch swipe with drag-vs-click detection). Admin JS is vanilla too, with jQuery UI sortable used only for drag-reordering slides; add/remove still work without it. Localised strings arrive via ciwL10n and ciwAdminL10n.
  • Progressive enhancement: photo/reel media are real <a> tags to the Instagram post; carousel slides are each a plain link and only the first slide shows without JS; Load More extras are hidden only once JS adds .ciw-feed--js; conditional admin fields render their initial state server-side.
  • Styling hooks: BEM-style ciw-* classes; column counts via CSS custom properties written inline on the grid (--ciw-cols-desktop, --ciw-cols-tablet, --ciw-cols-mobile); media locked to 3:4 via aspect-ratio: 3/4 with a padding-bottom fallback.
  • Images: lazy-loaded (loading="lazy"); cards use medium_large, the lightbox uses large.
  • No external requests: the plugin never calls Instagram or any other remote service, on the front end or in the admin.
  • Deactivation leaves all content and settings untouched. Uninstall keeps data by default; see below.

Uninstall and data removal

Deleting the plugin keeps every card and setting unless you opt in to removal. To remove them, do one of:

// wp-config.php
define( 'CIW_UNINSTALL_REMOVE_DATA', true );
wp option update ciw_uninstall_remove_data 1

With either in place, uninstalling deletes the ciw_settings option, the flag itself, and every ciw_post post with its meta. Media library files are never deleted.

Known limitations

  • The curated feed is queried with posts_per_page => -1 and then filtered in PHP (renderability check, then limit). This suits tens to low hundreds of cards, which is the intended use; it is not built for thousands.
  • Classic editor only, by design (see above). The card editor does not support the block editor.
  • One profile per site: a single avatar, name, handle and profile URL in settings.
  • No dedicated block or widget; use the shortcode.

Roadmap

Ideas under consideration, not commitments:

  • A block that wraps the shortcode with a live preview.
  • Paged or AJAX loading for very large feeds.
  • Bulk creation of cards from existing media library images.

Contributing

Coding standards are enforced with PHPCS (WordPress-Extra + WordPress-Docs):

composer install
composer lint

License

GPL-2.0-or-later. See LICENSE.

Originally built for a school website; published here with permission as a generic plugin.

Read the full README on GitHub →