About Opal
Opal converts your microblog posts from Twitter, Mastodon, Threads, and Nostr to Bluesky while preserving original timestamps.
How it works
- 1 Choose a platform
Pick Twitter, Mastodon, Threads, or Nostr.
- 2 Authenticate
Sign in with your AT Protocol identity via OAuth. Nothing is stored.
- 3 Upload your export
Drop in your archive file — everything is processed locally in your browser.
- 4 Import
Opal publishes your posts to your PDS with automatic rate-limit handling.
Privacy & data
Opal runs entirely in your browser. Your export files are parsed locally — they are never uploaded to any server run by this project.
The only network requests made are:
- Your PDS — Opal authenticates directly with your Personal Data
Server and publishes records there on your behalf, exactly as any other ATProto client
would. If you sign in via OAuth, Opal also creates a record in your repository using the
click.croft.toolkit.uselexicon each time you perform an import. This record contains the number of posts imported and a timestamp, helping you track your activity across the croft.click suite. - Google Fonts — the layout loads Inter and JetBrains Mono via Google Fonts. If you prefer not to make this request, you can self-host the fonts or use a content-blocking extension.
No cookies, no local storage, no fingerprinting.
Supported platforms
Twitter / X
Import your tweet archive from Twitter's data export file. Supports tweets, retweets, and quote tweets with facets.
Mastodon
Convert your ActivityPub outbox or CSV export from any Mastodon instance. Handles content warnings, media attachments, and poll metadata.
Threads
Bring over your Threads posts from Meta's data export. Supports text posts and media attachments.
Nostr
Convert your Nostr text notes (kind 1 events) to Bluesky posts.
What's converted
- Posts with original timestamps
- Links, mentions, and hashtags as facets
- Media attachments (images)
- Reply threads (where resolvable)
- Content warnings (Mastodon)
Not converted: Videos, polls, DMs, bookmarks, and circle-only posts.
OAuth scope
Opal requests minimal permissions to publish posts:
atproto repo:app.bsky.feed.post repo:click.croft.toolkit.use This allows reading your profile, writing posts to your repository, and logging tool usage. Your Bluesky profile is read directly from your PDS.
CLI / Local usage
Opal also ships as a Node.js command-line tool. This is useful if you prefer to run imports locally, need full control over batch settings, or want to automate things with scripts.
Prerequisites
Install & build
# Clone the repository
git clone https://github.com/ewanc26/pkgs.git
cd pkgs/packages/opal
# Install dependencies
pnpm install
# Build
pnpm buildUsage
# Interactive mode
pnpm start
# Import from Twitter archive
pnpm start -i tweets.js -h alice.bsky.social -p xxxx-xxxx-xxxx-xxxx -y
# Import from Mastodon outbox
pnpm start -i outbox.json -m mastodon -h alice.bsky.social -p xxxx-xxxx-xxxx-xxxx -y
# Sync (skip already-imported records)
pnpm start -i tweets.js -m sync -h alice.bsky.social -p xxxx-xxxx-xxxx-xxxx -y
# Preview without publishing
pnpm start -i tweets.js --dry-runKey flags
-i <path>Input file or directory-h <handle>ATProto handle or DID-p <password>App password (not your main password)-m <mode>twitter · mastodon · threads · nostr · sync-ySkip confirmation prompts--dry-runPreview without writing records-vVerbose / debug output-qQuiet mode (warnings & errors only)Full documentation is available at docs.ewancroft.uk/projects/opal.
Rate limits & PDS safety
ATProto PDS instances enforce rate limits on write operations. Exceeding them can temporarily affect all users on a shared PDS. Opal protects against this by:
- Reading the
ratelimit-*headers from each response - Maintaining a 15% headroom buffer before the quota ceiling
- Automatically adjusting batch size (up to 200 records) in real time
- Pausing immediately when the abort signal fires if you press Cancel
Licence
Opal is free software released under the GNU Affero General Public License v3.0 (AGPL-3.0-only).
In short: you are free to use, modify, and redistribute this software, but any modified version you run as a network service must also be released under the same licence with its source code made available.
The full licence text is included in the repository.
Credits
Created by
Contributors
Contributions via GitHub are always welcome. The full contributor list is maintained there.
Dependencies
- @atproto/api — ATProto client
- Svelte / SvelteKit — UI framework
- Lucide — icons
- Tailwind CSS — utility styles
- Bluesky — the
app.bsky.*lexicon this tool publishes to