SavedFlow documentation

A browser extension that organizes your saved Instagram posts into collections, with save dates, search, multi-select and drag and drop. Manifest V3, plain JavaScript, no build step.

Overview

Instagram has had a Saved button for years. It has one folder. SavedFlow adds a "Save to..." button next to Instagram's bookmark, keeps everything in local storage, and can import the saves you already have. There is no account, no server, and no analytics.

The repository is at github.com/jacksonfdam/SavedFlow. The extension source lives in the savedflow/ folder.

Install (unpacked)

Chrome, Edge or Brave

  1. Open chrome://extensions.
  2. Turn on Developer mode, top right.
  3. Click Load unpacked and select the savedflow/ folder.
  4. Pin the icon, open Instagram, save something.

Firefox

  1. Open about:debugging#/runtime/this-firefox.
  2. Load Temporary Add-on and pick savedflow/manifest.json.
The manifest ships Chrome's side_panel key only, because Chrome warns about the Firefox-only sidebar_action. The popup and content script work in Firefox as they are; for the sidebar there, add a sidebar_action block pointing at panel/index.html. Loaded temporarily, Firefox forgets the add-on on restart.

Using it

Saving

On any post, reel or the single-post view, a "Save to..." button appears next to Instagram's own bookmark, which is dimmed but left wired up so its state never breaks. Click it to pick a collection, toggle the post in or out of collections, or create a new collection inline.

Importing your existing saves

Open your own Saved pages and SavedFlow reads them from the page. The control at the bottom of the page shows live progress and has Pause, Resume and Cancel. It scrolls to the bottom and harvests links as it goes, because Instagram virtualises the grid and removes items from the DOM once they scroll out of view. The all-posts and audio pseudo-collections are skipped.

The side panel

Collections in a sidebar, a masonry grid or list, live search, a refresh button, multi-select with bulk move and remove, and drag and drop to file a post or reorder collections. Removing a post can also unsave it on Instagram, after confirmation; this needs a logged-in Instagram tab open.

Architecture

Everything attaches to one global SF object and loads as ordinary scripts in the order the manifest lists them. No bundler, no framework, no dependencies.

FileRole
shared/constants.jsStorage keys, the cross-browser API handle, bookmark aria-label strings across locales, message names, the Instagram web app id.
shared/utils.jsUUIDs, the relative date formatter, shortcode extraction, HTML escaping.
shared/storage.jsPromise wrapper over chrome.storage.local. Counts derived on read; idempotent import; dedupePosts().
content/observer.jsMutationObserver that finds posts and re-scans on SPA navigation. Keys off semantic attributes, never class names.
content/injector.jsDims the native bookmark and inserts the custom button. Guards against duplicates on a shared bookmark.
content/overlay.jsThe floating collection picker anchored to the button.
content/importer.jsReads the Saved pages, scrolls and harvests, with the pause/resume/progress control.
content/igapi.jsConverts a shortcode to a media id and calls Instagram's private unsave endpoint from the logged-in tab.
content/index.jsWires observer to injector, starts the importer, injects through a small queue.
content/styles.cssAll injected styles. No inline styles at runtime, because Instagram's CSP blocks them.
background/service-worker.jsOpens the side panel, initialises empty storage on install, relays save events to the panel.
popup/, panel/The toolbar popup and the side panel UI, vanilla JS reusing the shared/ modules.

Thumbnails get special handling. Instagram's CDN sends a Cross-Origin-Resource-Policy header, so a plain <img> is blocked from this extension's origin. The panel fetches each image through the CDN host permission, turns it into a blob URL, and caches it for the session, six requests at a time.

Data model

Stored in chrome.storage.local under three keys: collections, savedPosts, settings.

collection = { id, name, emoji, color, createdAt, order, igRef? }
post       = { id, instagramPostId, postUrl, imageUrl, caption,
               username, userAvatarUrl, savedAt, collectionIds[] }

Collection counts are computed from the posts on read, not stored, so they cannot drift. Imports match collections by name and posts by shortcode, so re-running never duplicates.

Permissions

PermissionWhy
storageStores collections and saved-post records locally.
sidePanelShows the organizer as a Chrome side panel.
https://www.instagram.com/*Runs the content script: the Save button, import, and unsave.
https://*.cdninstagram.com/*, https://*.fbcdn.net/*Lets the panel fetch post thumbnails, which the CDN blocks cross-origin otherwise.

No tabs, no cookies, no remote code.

Build & publish

Run ./build.sh from the repo root. It reads the version from manifest.json and writes dist/savedflow-<version>.zip with manifest.json at the archive root, which is what the stores want.

A GitHub Actions workflow at .github/workflows/release.yml builds the zip on a version tag (v1.0.0), attaches it to a GitHub Release, and, if credentials are set, publishes to both stores.

Chrome Web Store

Via chrome-webstore-upload-cli. Needs the repository variable CHROME_EXTENSION_ID and the secrets CHROME_CLIENT_ID, CHROME_CLIENT_SECRET, CHROME_REFRESH_TOKEN. There is a one-time 5 dollar developer fee and every submission is reviewed, so "automatic" means "queued for review".

Firefox Add-ons

Via web-ext sign --channel=listed. Needs the secrets AMO_JWT_ISSUER and AMO_JWT_SECRET. The first listed version also needs metadata that AMO asks for.

Limitations

Privacy

No analytics, no account, no server. Your data stays in chrome.storage.local on your machine. The only network requests SavedFlow makes go to Instagram and its CDN, on your behalf: reading your saves, fetching thumbnails, and calling the unsave endpoint when you remove a post. Nothing is sent anywhere else.

SavedFlow is not affiliated with, endorsed by, or connected to Instagram or Meta. MIT licensed.