ElectricBlaze feed schema v1: one JSON shape for social posts

The contract between ElectricBlaze and your component. Render type, filter by format, and never parse a platform payload in your UI.

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 and format in the Instagram demo feed
typeformatInstagram calls itRenderPosts in the sample
imagepostPhoto post<img src={thumbnailUrl}>6
videoreelReelPoster from thumbnailUrl, "Reel" badge, 9:162
videopostVideo in the feedPoster from thumbnailUrl, play badge2
carouselpostAlbum (CAROUSEL_ALBUM)First item, count badge from media.length2

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

Public schema files
FileWhat it isUse it for
schema.jsonJSON Schema, draft 2020-12, $id on this siteValidation in CI, form generators, other languages
sample-feed.jsonThe 12-post demo feed, byte for byte the file the CLI shipsFixtures, Storybook, design work
feed.d.tsTypeScript types with commentsImport as import type { Feed } from "./lib/eb/feed"
validate.jsDependency-free validator used by doctorRuntime 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 media fields to schema v1
Instagram fieldSchema v1Note
media_type IMAGE / VIDEO / CAROUSEL_ALBUMtype image / video / carouselAlways present
media_product_type REELSformat: "reel"Documented for Facebook Login only (Meta: IG Media reference, checked 2026-09-18)
thumbnail_url, else media_urlthumbnailUrlthumbnail_url exists only on videos
media_urlmedia[0].urlLeft out for copyright-flagged media
children{media_type,media_url,thumbnail_url}media[]Only when expanded in fields=
permalinkurl
captiontextEmpty string when absent
timestamppublishedAtNormalized to ISO 8601 with Z
like_count, comments_countstats.likes, stats.commentsOptional

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 versus schema v1
Widget data (internal)Schema v1 (public contract)
Envelope{"data": [...], "paging": {...}}{"schemaVersion": 1, "origin": {...}, "posts": [...]}
Post fieldsid, caption, media_type, media_url, permalink, timestamp, usernameSee above
ReelsNot marked: a reel is a VIDEOformat: "reel" when the source provides it
Carousel itemsNot expandedmedia[]
AddressSigned 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.

FAQ

Why both type and format?
type is closed and drives layout: five values that every renderer handles. format is open and repeats the platform term, so a new format never breaks a grid.
Is thumbnailUrl always there?

For image, video and carousel posts, yes; the schema makes it required for those types. Text and link posts may omit it.

Can I extend the schema?

Do not edit feed.d.ts; it is the contract. Wrap the post in your own type or read extra data from raw.

Does the widget return this format?

No. The widget renders its own data inside the page. Schema v1 is what the CLI writes and what the 0.2 JSON API will serve.

Where is the JSON Schema hosted?

At https://electricblaze.com/developer/instagram-feed/schema.json, which is also its $id.