npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

threads-backup

v0.2.0

Published

Back up your Threads posts and media with the official Threads API.

Readme

threads-backup

Back up your own Threads posts and attached images, videos, GIFs, thumbnails, and carousel media with the official Threads API.

Open the illustrated setup guide.

Requirements

  • Node.js 24 or newer
  • A Meta app with the Threads API use case
  • cloudflared when using the default Quick Tunnel login flow

Use the Threads App ID and Threads App Secret shown in the Meta dashboard. They are not the app's general Facebook credentials. In development mode, add your Threads account as an app tester and accept the invitation in Threads.

Install

npm install --global threads-backup

Create a .env file in the directory where backups should be saved:

THREADS_APP_ID=your_threads_app_id
THREADS_APP_SECRET=your_threads_app_secret
PORT=8787

The CLI stores each account's credentials in .credentials/{username}.json and backups in backups/{username}/ in that same directory. Do not commit any of these files.

First login with a Quick Tunnel

Install cloudflared if it is not already on PATH:

# macOS
brew install cloudflared

# Windows
winget install --id Cloudflare.cloudflared

Run the CLI:

threads-backup --account your_threads_username

When authorization is needed, the CLI starts a local callback server and runs:

cloudflared tunnel --url http://127.0.0.1:8787 --no-autoupdate --output json

The CLI prints a callback such as:

https://random-words.trycloudflare.com/oauth/callback

Add that exact URL under Meta App > Threads API > Redirect Callback URLs, return to the terminal, and press Enter. Open the authorization URL printed by the CLI. After login, the CLI exchanges the code for a long-lived token, looks up the Threads username, saves it to .credentials/{username}.json, stops its tunnel, and immediately starts the backup.

Quick Tunnel hostnames are random and intended for development or testing. A new callback must be registered whenever a new Quick Tunnel is needed. The CLI normally avoids this by refreshing an unexpired long-lived token before it expires. See Cloudflare's Quick Tunnel documentation.

Fixed callback URL

For a callback that only needs to be registered once, run a named or remotely managed Cloudflare Tunnel separately and add its HTTPS URL to .env:

THREADS_REDIRECT_URI=https://threads-backup.example.com/oauth/callback
CLOUDFLARED_TUNNEL=threads-backup

The fixed tunnel's public hostname must use the same URL and route to http://127.0.0.1:PORT. The CLI checks CLOUDFLARED_TUNNEL with cloudflared tunnel list: it reuses an active connector, or securely obtains a temporary credentials file and starts an inactive connector. A connector started by the CLI is stopped after OAuth; an already-running connector is left alone.

Usage

# Back up every account saved in .credentials/
threads-backup

# Back up one account, authorizing it first when needed
threads-backup --account your_threads_username

# Apply backup modes to every saved account
threads-backup --resume

# Re-fetch every post and attached media
threads-backup --full-backup

# Save backups in another folder
threads-backup --backup-folder ./archive

threads-backup --help
threads-backup --version

Without --account, accounts are read from .credentials/*.json, sorted by username, and backed up one at a time. If one account fails, the remaining accounts still run and the command exits with a failure status afterward. Use -a username or --account username to select or authorize one account.

The default backup mode is incremental: posts are returned newest first, and the CLI stops when it reaches the first post whose JSON file already exists. --resume scans all pages, skips posts whose JSON file exists, and backs up missing posts left by an interrupted run. --full-backup overwrites files returned by the API but does not delete unknown files from an existing backup directory.

Debug logging

Enable detailed post and media download logs with NODE_DEBUG=threads-backup:

NODE_DEBUG=threads-backup threads-backup

Output

Post timestamps are converted to UTC:

backups/
└── your_threads_username/
    └── 2023/
        └── 2023-07-06-04-35-02-17977704596464643/
            ├── 17977704596464643.json
            ├── 17977704596464643-media.jpg
            ├── 17977704596464643-thumbnail.jpg
            └── 17977704596464644-media.mp4

Each JSON file contains:

{
  "id": "17977704596464643",
  "timestamp": "2023-07-06T04:35:02+0000"
}

Media attached directly to the post and its carousel children is downloaded. Media belonging to quoted or reposted third-party posts is not downloaded.

Breaking change

Backups now use backups/{username}/{year}/ instead of backups/{year}/. Existing backup folders are not migrated or checked, so the first account-scoped run can download those posts again. The old folders are left untouched.

Security and API access

  • .credentials/ uses 0700; each {username}.json uses owner-only 0600.
  • Access tokens and app secrets are never included in operational logs or API query strings.
  • The OAuth callback uses a cryptographically random state value.
  • Do not commit .env or .credentials/.
  • Quick Tunnels expose the callback server publicly only during authorization; the CLI stops the child process afterward.

threads_basic is the only requested permission. App testers can use the app while it is in development mode. Allowing arbitrary users to authenticate can require switching the Meta app live and completing the applicable Meta App Review and data-handling requirements.

Development

pnpm check
pnpm test
pnpm build
node dist/cli.js --help

Runtime code has no third-party dependencies. Tests use the Node.js test runner.

License

MIT