omnipost / instagram
Omnipost Instagram: the account's feed, the publication of pictures, carousels and reels, and the long-lived token's refresh, through the Instagram API with Instagram Login.
Requires
- php: >=8.2
- glitchr/omnipost: ^1.0@dev
- symfony/http-client: ^6.4|^7.0|^8.0
Requires (Dev)
- phpunit/phpunit: ^11.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-03 16:12:09 UTC
README
Instagram for glitchr/omnipost: the account and its
feed, pictures, carousels, reels and stories published, the long-lived token refreshed and the
account connected from a back office - through the Instagram API with Instagram Login
(graph.instagram.com), no Facebook Page needed.
omnipost: providers: instagram: factory: instagram options: access_token: '%env(default::INSTAGRAM_ACCESS_TOKEN)%' # the long-lived token: everything but connecting app_id: '%env(default::INSTAGRAM_APP_ID)%' # the Instagram app's id and secret: authorizationUrl(), exchange() app_secret: '%env(default::INSTAGRAM_APP_SECRET)%' user_id: me # the account the token belongs to share_to_feed: true # a reel also on the profile's grid api_version: v23.0 base_uri: https://graph.instagram.com
No option is required to build the provider: capabilities() and authorizationUrl() need no
token, and a call that needs one and finds none throws InvalidConfigException.
Publishing, in three steps
$publication = $instagram->publish($post->for(Platform::INSTAGRAM)); // PENDING, the container's id $publication = $instagram->status($publication->id); // PROCESSING while Instagram transcodes $publication = $instagram->status($publication->id); // FINISHED: published here - PUBLISHED, the media's id, its permalink
publish()checks the post (InvalidPostException, nothing sent), then creates a container: Instagram fetches the media from its URL (video_url,image_url). A carousel is a container per item (is_carousel_item), then theirs (children); a story ismedia_type=STORIES.status(container)reads it:IN_PROGRESSis PROCESSING,ERROR/EXPIREDFAILED with Instagram's reason.- Once the container is
FINISHED,status()publishes it (media_publish) and answers PUBLISHED with the media's id - the Publication's id changes there: keep the latest one.status(mediaId)answers PUBLISHED with the permalink. Two workers must not poll one container at once (it would be published twice): hold a lock aroundstatus().
delete() throws NotSupportedException: the API cannot delete a post, the account does it in the
application. Instagram also caps the publications through the API per account and per 24 hours
(GET /{user}/content_publishing_limit says where the account stands).
capabilities() are one set for every kind - the Validator's - so mediaMaxCount (10) is the
carousel's, 3 s - 15 min the reel's, 9:16 the reel's and the story's ratio, 4:5 and 1:1 the
picture's. validate() adds what depends on the kind (one media unless a carousel, 2 to 10 for
one, a reel is a 9:16 video) and is what publish() runs.
Reading
account(): the username, name, picture, followers, posts count, account type. feed($cursor, $limit): the posts, newest first - a reel (media_product_type REELS) is REEL, another video
VIDEO, CAROUSEL_ALBUM CAROUSEL with its children, a picture IMAGE - with likes and comments, and
next the cursor of the next page.
To show someone else's public post, no API is needed: Instagram's embed (the
<blockquote class="instagram-media" data-instgrm-permalink="..."> and //www.instagram.com/embed.js).
What it takes
- A professional Instagram account (Business or Creator: Settings → Account type).
- A Meta app (developers.facebook.com, type Business) with the Instagram API with
Instagram Login product, the permissions
instagram_business_basicandinstagram_business_content_publish, the site's redirect URI declared. - While the app is in development mode, the artist's account is added as an Instagram Tester (App roles → Roles) and accepts the invitation (Instagram → Settings → Apps and websites). That is all a site posting on its own account needs: no App Review. App Review (2 to 4 weeks, a screencast of every permission in use, a privacy policy) only comes in to serve accounts that are not the app's testers - third parties.
- A token: generated in the app's dashboard for the tester's account, or obtained with
authorizationUrl()+exchange()(a short-lived token, then the long-lived one). It lives 60 days;refresh()gives 60 more to a token at least 24 hours old and not dead yet - a monthly cron, the new token stored. A dead token is anUnauthorizedException: the account is connected again. - The media at a public URL Meta's servers can fetch (a signed, temporary URL will do). Reels: MP4 or MOV, H.264 and AAC, 9:16, 3 seconds to 15 minutes, at most 1 GB; pictures: JPEG, at most 8 MB, 4:5 to 1.91:1 (here 4:5 and 1:1, the formats a site renders).
License: LGPL-3.0-or-later.