Library / SDK
snarfed/bridgy avatar
snarfed/bridgy

Bridgy: a hosted bridge between your own site and the social networks

📣 Connects your web site to social media. Likes, retweets, mentions, cross-posting, and more...

847 stars59 forksPythonCC0-1.0

At a glance

What is it?
Bridgy is an IndieWeb service that pulls likes, reposts and mentions back from social networks into your own site, and can cross-post from your site out to those networks. It is a Python Flask application on Google App Engine with a Firestore datastore, a task queue for background work, and a browser extension for the account linking.
Who is it for?
Bridgy suits an independent site that publishes with its own domain and wants its social network reactions to land on its own pages as webmentions, and it suits you if you are willing to hand account authorisation to a hosted service. It does not suit a site on a closed platform such as a Medium or Substack account, because the value of the backfeed comes from owning the URL, and it does not suit an organisation that cannot store social credentials in a third party service.
Can I use it commercially?
Yes. CC0-1.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 5 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 25, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Backfeed, POSSE and webmentions as a service

Bridgy connects your web site to social media, handling likes, reposts, mentions and cross-posting. The project is part of the IndieWeb ecosystem, and the README names the three IndieWeb concepts it implements: backfeed, POSSE and webmention support.

Backfeed is the less familiar of the three and the most distinctive. It is the reverse direction of the usual syndication. Instead of copying your post from your site to a social network and leaving it there, Bridgy watches the social network for reactions to your posts and brings those reactions back to your site, where they arrive as webmentions on the original post. The reaction becomes a comment on your own page, attributed to the person who liked or reposted it.

POSSE is the other direction, post-own-syndicate-elsewhere, and Bridgy does cross-posting as a service for that too. Webmention support is the transport for both: it is how a mention on one site is delivered to another.

The audience is therefore the independent publisher with their own domain. Someone with a site at their own URL can get their Mastodon boosts, their Tumblr likes and their Twitter reposts to appear as replies under the original post on their own site, which is the point of the whole IndieWeb approach: the canonical copy lives at your address and everything else is a copy or a notification.

The service runs at brid.gy, with user documentation there and developer documentation on readthedocs for anyone contributing.

One point about the shape of the code is worth stating early, because it explains the rest of the setup. The pyproject.toml carries a comment that says bridgy is an application, not a packaged library, and that the file is just for managing dependencies.

A Flask app split into a web service and a background worker

The repository is a single Python application, but it is not one process. The top level listing shows two separate App Engine service definitions, app.yaml and background.yaml, plus cron.yaml and queue.yaml for the scheduled and queued work, and separate entry modules flask_app.py and flask_background.py.

The split is visible in the deployment instructions. To run the app locally you start it with FLASK_APP defaulting to the web application.

shell
GAE_ENV=localdev FLASK_ENV=development flask run -p 8080

To run the background task processing service you restart it with a different FLASK_APP value.

shell
GAE_ENV=localdev FLASK_ENV=development flask run -p 8080

The same distinction shows up in the source layout. publish.py handles the outbound direction, webmention.py the inbound delivery, and handlers.py, pages.py, admin.py and models.py the request handling, templates and datastore models. Per-network modules sit alongside them: mastodon.py, twitter.py, tumblr.py, reddit.py, flickr.py, github.py, bluesky.py and wordpress_rest.py, each of which knows how to speak one platform's API. superfeedr.py and blog_webmention.py handle feed discovery and blog-to-blog webmentions, while indieauth.py and micropub.py handle the IndieAuth and Micropub protocols.

The background service is not an optional extra. Polling the social networks for new reactions is inherently a scheduled, slow, network-bound job, so it is separated from the request path and driven by a task queue. The queue configuration and the Tasks API dependency in the manifest both point at Google Cloud Tasks as the mechanism.

Local development needs the Firestore emulator and a gcloud project setting

The setup instructions are concrete, which makes them easy to follow and also tell you what the stack is.

The prerequisites are the Google Cloud SDK and the Firestore emulator, which is installed with a gcloud components install command.

sh
gcloud config set project brid-gy
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
# needed to serve static files locally
ln -s local/lib/python3*/site-packages/oauth_dropins/static oauth_dropins_static

The last line is a detail that would otherwise cost an afternoon. The oauth-dropins package ships static files that the app serves, and in a virtualenv they live inside site-packages under a path that varies with your Python version, so the setup creates a symlink with a glob to point at them. The documentation also notes that you can use uv instead of pip, but if you do you must pass it --no-sources.

Running the tests means starting the emulator in the background, discovering the tests, and then killing the emulator.

sh
gcloud emulators firestore start --host-port=:8089 --database-mode=datastore-mode < /dev/null >& /dev/null &
python3 -m unittest discover -s tests -t .
kill %1

The datastore-mode flag matters. Firestore in datastore mode is what App Engine's NDB layer expects, so the emulator is started that way rather than in the newer native Firestore mode.

The test dependency list in the pyproject file is empty, which is a bit misleading until you notice that the requirements files are what actually carry the test tooling. The README asks contributors to include or update a test for new code in a pull request.

The browser extension has its own toolchain, npm based, with its own test command and a way to run a single test by passing a name fragment to it.

Testing a poll task means pulling a task body out of the log

The most distinctive part of the contributor documentation is the procedure for testing a background task, and it shows how tightly the service is coupled to App Engine's task queue internals.

You run the app, trigger the task, and find the line in the log that says what would be added to the queue. The log line contains a serialized task with the relative URI and the request body, so the workflow is to pull those two pieces out and assemble them into a curl command against your local server.

The log line looks like this.

code
INFO:root:Would add task: projects//locations/us-central1/queues/poll {'app_engine_http_request': {'http_method': 'POST', 'relative_uri': '/_ah/queue/poll', 'app_engine_routing': {'service': 'background'}, 'body': b'source_key=agNhcHByFgsSB1R3aXR0ZXIiCXNjaG5hcmZlZAw&last_polled=1970-01-01-00-00-00', 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}}, 'schedule_time': seconds: 1591176072

And the curl command you build from it is this.

code
curl -d 'source_key=agNhcHByFgsSB1R3aXR0ZXIiCXNjaG5hcmZlZAw&last_polled=1970-01-01-00-00-00' \
  http://localhost:8080/_ah/queue/poll

The instruction to build the command but not run it yet, and to run the background service with FLASK_APP set to background before running it, is the kind of precise sequencing that only appears in documentation written by someone who has done it.

The two things worth taking from this are that a task is addressed by a source key and a last polled timestamp, and that the delivery mechanism is an HTTP POST to an internal queue URI with form encoded content, with a routing hint naming the background service. Nothing about that is portable outside App Engine, and that shapes the self hosting story.

The dependency set is a set of local checkouts of the author's own libraries

The dependency management is unusual and deliberate, and understanding it explains the whole development workflow.

The pyproject.toml lists direct dependencies under an app extra, including the author's own packages: granary, oauth-dropins, mf2util, pywebutil, lexrpc and flask-gae-static, plus third party ones such as flask, google-cloud-ndb, google-cloud-tasks, gunicorn, requests-oauth2client and cachetools. The package field is set to false, meaning the project is not built or installed as a wheel.

The uv sources section maps each of those own packages to an editable local path, with a sibling checkout of the repository expected next to the bridgy directory. The comment above the extra explains the arrangement: the own packages are used at head via the editable local checkouts, while the deploy-time git refs live in requirements.txt.

That split is the key. requirements.txt pins every own library to a GitHub URL, including arroba, brevity, carbox, flask-gae-static, granary, lexrpc, mf2util, oauth-dropins, secp256k1-py and webutil, and then includes the generated requirements.pinned.txt, which the comment says is produced with a uv pip compile command.

The requirements file also carries a warning about App Engine and Cloud Build caching GitHub dependency URLs aggressively, so that a new commit on a branch can be masked by a stale cached version, and it points at the --no-cache flag on the deploy command as the way to bust that cache. That is a specific, real failure mode of this deployment setup.

For a contributor working on a change that spans libraries, the README describes the workflow: clone granary and oauth-dropins elsewhere, uninstall the installed versions, install them in editable mode from the local checkout, and relink the static files directory.

code
pip uninstall -y oauth-dropins
pip install -e <path-to-oauth-dropins-repo>
ln -sf <path-to-oauth-dropins-repo>/oauth_dropins/static oauth_dropins_static

The same pattern is given for granary. So the library work happens in the library repositories, and bridgy is the application that consumes them.

Where Bridgy is the wrong tool

The first wrong-tool case is a site you do not own the domain for. The entire value of the backfeed direction is that the reaction lands on your own page at your own URL. On a platform that hosts your writing for you, you do not control the page, and Bridgy has nothing to attach a webmention to.

The second case is an organisation that cannot let a third party hold social account credentials. Bridgy's model is that you authorise it against your accounts, and the browser extension exists precisely to capture that authorisation and send it. The documentation is candid about the security side of this: the extension logs in the JavaScript console are invaluable for debugging, they can be exported to a file, and that file must be emailed to the maintainer directly because it contains sensitive tokens and cookies and must never be attached to a public GitHub issue. Handling credentials for third party accounts on shared infrastructure is a decision with legal and security weight, not a technical preference.

The third case is a team that wants to self host the service in production. The documentation says deploying to your own App Engine project can be useful for testing but is not recommended for production. The steps are described in detail, including updating TASKS_LOCATION in util.py, adding the background domain to OTHER_DOMAINS, setting host_url in tasks.py, and deploying with the gcloud beta app deploy command. It is a supported experiment, not a supported production arrangement.

There is also a hard platform dependency worth naming. The datastore is Google Cloud NDB on Firestore, the task queue is Google Cloud Tasks, and the deployment target is App Engine. The Firestore emulator is required to run the tests at all, so the local development loop needs the Google Cloud SDK even for a contributor who never intends to deploy. The stack is coherent, but it is not portable to another cloud without rewriting the persistence and queue layers.

On licensing, the project is placed in the public domain and may also be used under the CC0 License, which is one of the most permissive choices available. The repository also carries a LICENSE file. The version of the pyproject is 1.0 and the project requires Python 3.14 or newer, so a deployment has to run a very recent interpreter.

Editorial conclusion

Bridgy suits an independent site that publishes with its own domain and wants its social network reactions to land on its own pages as webmentions, and it suits you if you are willing to hand account authorisation to a hosted service. It does not suit a site on a closed platform such as a Medium or Substack account, because the value of the backfeed comes from owning the URL, and it does not suit an organisation that cannot store social credentials in a third party service. Before adopting, read the user docs at brid.gy/about, decide whether the hosted service or your own App Engine project is right for you since the project warns against self deployment for production, and note that the repository requires Python 3.14 or newer.

Frequently asked questions

What does Bridgy do?

Bridgy connects your web site to social media, handling likes, reposts, mentions and cross-posting. In IndieWeb terms it offers backfeed, POSSE and webmention support as a service, so reactions on a social network can arrive on your own page as a webmention.

How do I run the Bridgy tests locally?

Install the Google Cloud SDK and the Firestore emulator, create a virtualenv, install the requirements, then start the emulator in datastore mode on port 8089, run python3 -m unittest discover -s tests -t ., and kill the emulator. The README also notes you can use uv instead of pip if you pass it --no-sources.

How do I test a background poll task in Bridgy?

Run the app, trigger the task, and find the Would add task line in the log. It contains the relative_uri and the request body, so you combine them into a curl command against localhost:8080, then restart the app with FLASK_APP=background before running it.

Can I deploy Bridgy to my own App Engine project?

The documentation says deploying to your own App Engine project can be useful for testing but is not recommended for production. It requires a project with the Tasks API activated, TASKS_LOCATION updated in util.py, your background domain added to OTHER_DOMAINS, host_url set in tasks.py, and a gcloud beta app deploy run.

What license is Bridgy released under?

The project is placed in the public domain and may also be used under the CC0 License. The pyproject file requires Python 3.14 or newer.

Official sources

  1. Issues
  2. License: CC0-1.0
  3. Project website
  4. README
  5. snarfed/bridgy on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/snarfed-bridgy.svg)](https://hysenlabs.com/projects/snarfed-bridgy)

Was this useful?