At a glance
Status, checked 2026-09-18: schema v1 is what npx electricblaze add writes today (as demo data) and what the 0.2 JSON API will return. The widget embed uses its own internal data, shown at the end of this page.
- Use it when
- you write a component that renders social posts and want one shape for posts, reels and carousels, with the rendering decision in a single field.
- Do not use it when
- you consume the raw Instagram API yourself. Its envelope is different; see media fields.
- What the human does
- nothing. The schema, the types and the sample are public files.
- Next step
- download schema.json and sample-feed.json, or run
npx [email protected] preview instagram --json.
The shape in thirty seconds
A feed is an origin (the account) plus a list of posts. Every post carries what a renderer needs as required fields. Anything specific to one platform stays in raw.
lib/eb/feed.d.ts (excerpt)
interface Feed {
schemaVersion: 1;
source: "instagram" | "youtube" | "tiktok" | (string & {});
origin: { kind: "profile" | "hashtag" | "playlist" | "board" | "channel";
id: string; name: string; displayName: string; url: string; avatarUrl?: string };
posts: Post[];
fetchedAt: string; // ISO 8601
demo: boolean; // true until an account is connected
nextCursor?: string;
}
interface Post {
id: string; source: string;
type: "image" | "video" | "carousel" | "text" | "link"; // what to render
format: string; // "post" | "reel" on Instagram
status: "published" | "live" | "upcoming";
url: string; // permalink on the platform
text: string; // caption, may be empty
media: { url: string; mime: string; width?: number; height?: number; alt?: string; durationSec?: number }[];
thumbnailUrl?: string; // required for image, video and carousel
aspectRatio?: number; // width / height: 1, 0.8, 0.5625, 1.7778
durationSec?: number;
publishedAt: string;
stats?: { likes?: number; comments?: number; views?: number; shares?: number };
raw?: unknown; // original platform payload, untouched
}
The full file with comments is feed.d.ts on GitHub. The CLI copies it into your project as lib/eb/feed.d.ts.
type decides layout, format decides badges
Two fields describe a post, and they answer different questions. type tells the component which element to draw. format repeats what the platform calls the post, so you can filter and label it.
| type | format | Instagram calls it | Render | Posts in the sample |
|---|---|---|---|---|
image | post | Photo post | <img src={thumbnailUrl}> | 6 |
video | reel | Reel | Poster from thumbnailUrl, "Reel" badge, 9:16 | 2 |
video | post | Video in the feed | Poster from thumbnailUrl, play badge | 2 |
carousel | post | Album (CAROUSEL_ALBUM) | First item, count badge from media.length | 2 |
Layout code should switch on type only. A new platform format, such as a YouTube short, then renders correctly without a code change. thumbnailUrl is always present for the three visual types, so a grid never needs to inspect media.
One post, as JSON
A reel from sample-feed.json. Its media file is vertical, so aspectRatio is 0.5625 (9:16).
posts[1] of sample-feed.json
{
"id": "demo-ig-02",
"source": "instagram",
"type": "video",
"format": "reel",
"status": "published",
"url": "https://electricblaze.com/demo/instagram#post-02",
"text": "How we cup a new lot in under 60 seconds.",
"media": [
{
"url": "https://cdn.jsdelivr.net/gh/ElectricBlaze/electricblaze@89e2819/demo/media/reel-01.mp4",
"mime": "video/mp4",
"width": 540,
"height": 960,
"durationSec": 10
}
],
"thumbnailUrl": "https://cdn.jsdelivr.net/gh/ElectricBlaze/electricblaze@89e2819/demo/media/reel-01.jpg",
"aspectRatio": 0.5625,
"durationSec": 10,
"publishedAt": "2026-09-10T16:05:00Z",
"stats": { "likes": 1287, "comments": 64, "views": 18450 }
}
The demo posts are synthetic: a coffee roaster with twelve posts, photos under 1:1, 4:5, 9:16 and 16:9, two reels, two videos and two carousels. The media files are licensed from Pexels and Blender Foundation projects and served from jsDelivr, pinned to a commit.
Files you can use
| File | What it is | Use it for |
|---|---|---|
| schema.json | JSON Schema, draft 2020-12, $id on this site | Validation in CI, form generators, other languages |
| sample-feed.json | The 12-post demo feed, byte for byte the file the CLI ships | Fixtures, Storybook, design work |
| feed.d.ts | TypeScript types with comments | Import as import type { Feed } from "./lib/eb/feed" |
| validate.js | Dependency-free validator used by doctor | Runtime checks without a schema library |
Validate a file with Ajv, which supports draft 2020-12:
curl -sO https://electricblaze.com/developer/instagram-feed/schema.json
npx -p ajv-cli@5 -p ajv-formats ajv validate --spec=draft2020 -c ajv-formats -s schema.json -d lib/eb/demo/instagram.json
# lib/eb/demo/instagram.json valid
We ran this on 2026-09-18: the sample passes, and a post with thumbnailUrl removed fails with must have required property 'thumbnailUrl'.
Or let the CLI do it: npx electricblaze doctor --json validates whichever feed file it finds in the project and names the field that fails.
How Instagram fields map onto it
This is how an Instagram post becomes a schema v1 post. Field details and their caveats are on media fields.
| Instagram field | Schema v1 | Note |
|---|---|---|
media_type IMAGE / VIDEO / CAROUSEL_ALBUM | type image / video / carousel | Always present |
media_product_type REELS | format: "reel" | Documented for Facebook Login only (Meta: IG Media reference, checked 2026-09-18) |
thumbnail_url, else media_url | thumbnailUrl | thumbnail_url exists only on videos |
media_url | media[0].url | Left out for copyright-flagged media |
children{media_type,media_url,thumbnail_url} | media[] | Only when expanded in fields= |
permalink | url | |
caption | text | Empty string when absent |
timestamp | publishedAt | Normalized to ISO 8601 with Z |
like_count, comments_count | stats.likes, stats.comments | Optional |
Two shapes you may meet today
The ElectricBlaze widget predates schema v1. Its script loads the Graph API response as it comes, and your code never sees it. We list it so that an agent inspecting network traffic does not mistake it for the public format.
| Widget data (internal) | Schema v1 (public contract) | |
|---|---|---|
| Envelope | {"data": [...], "paging": {...}} | {"schemaVersion": 1, "origin": {...}, "posts": [...]} |
| Post fields | id, caption, media_type, media_url, permalink, timestamp, username | See above |
| Reels | Not marked: a reel is a VIDEO | format: "reel" when the source provides it |
| Carousel items | Not expanded | media[] |
| Address | Signed URL, minted per page load, expires within hours (observed 2026-09-17) | Your file today; the 0.2 API later |
Code against schema v1 only. Breaking changes will raise schemaVersion; fields a renderer does not know should be ignored, not rejected.