Crosspost Action
ActionsTags
(2)Crosspost your latest content to social media directly from GitHub Actions.
This action fetches URLs from a sitemap.xml or RSS/Atom feed, filters them by age, and posts them to your configured social networks using humanwhocodes/crosspost.
- Supports both sitemaps and RSS/Atom feeds.
- Crosspost to multiple networks in one go (Twitter/X, Mastodon, Bluesky, LinkedIn, etc.).
- Filter by age (
since+since-unit) and by URL include/exclude patterns. - Run in dry-run mode to safely preview what would be posted.
- Flexible failure strategies: stop on error or continue with other posts.
- Prefill messages with blog post metadata: use
{description}and{tags}in your message template to automatically include the post's meta description and tags.
Start with dry-run: true to ensure you don’t flood your social networks with hundreds of posts on the first run.
Once you confirm the filtering works as expected, remove dry-run or set it to false.
Run this action once a day at a time when your audience is most likely online — for example:
- 09:00 (morning commute)
- 17:00 (after work)
Example scheduler trigger:
on:
schedule:
- cron: '0 9 * * *' # every day at 09:00 UTCHere are the most important inputs (see action.yml for the full list):
| Input | Required | Description |
|---|---|---|
feed-url |
✅ Yes | URL of the sitemap (.xml) or RSS/Atom feed to fetch posts from. |
since |
No | How far back to fetch posts. Use with since-unit. Default: 1. |
since-unit |
No | Unit for since: minutes, hours, days, weeks, months, years. Both singular and plural forms are accepted. Default: day. |
limit |
No | Maximum number of posts to publish. Do not flood! |
failure-strategy |
No | Either fail (default) or continue when a single post fails. |
dry-run |
No | If true, shows what would be posted without actually posting. |
exclude-urls |
No | Newline-separated list of URL patterns to exclude (supports wildcards like *). |
filter-urls |
No | Newline-separated list of URL substrings that must be present to include the post. |
The action accepts credentials as inputs and maps them to environment variables expected by crosspost. Examples:
-
Mastodon
mastodon-access-token→MASTODON_ACCESS_TOKENmastodon-host→MASTODON_URL- more on configuration details.
-
Twitter/X
twitter-access-token-keytwitter-access-token-secrettwitter-api-consumer-keytwitter-api-consumer-secret- more on configuration details.
-
Bluesky
bluesky-hostbluesky-identifierbluesky-password- more on configuration details.
-
LinkedIn
linkedin-access-token- more on configuration details.
-
Discord Bot
discord-bot-tokendiscord-channel-id- more on configuration details.
-
Discord Webhook
discord-webhook-url- more on configuration details.
-
Dev.to
devto-api-key- more on configuration details.
-
Telegram
telegram-bot-tokentelegram-chat-id- more on configuration details.
-
Slack
slack-tokenslack-channel- more on configuration details.
...and so on for other networks supported by crosspost.
Use GitHub secrets for these values.
This action can send Webmentions to notify other sites about your posts. There are two main approaches:
1. Endpoint-based webmentions (recommended for Brid.gy)
If you want to send webmentions through a centralized endpoint like Brid.gy:
- Set
webmention-endpointto the endpoint URL (e.g.,https://brid.gy/publish/webmention). - Set
webmention-target-hoststo the social network targets (e.g.,https://brid.gy/publish/bluesky,https://brid.gy/publish/mastodon). - The action will send a webmention to the endpoint with your post as the source and each target as the target.
message input. They fetch and parse your post HTML for microformats (h-entry, e-content, etc.) to determine the content. See Brid.gy's microformats documentation for details on how your post structure affects the result.
2. Dynamic webmentions (scan content for links)
To automatically notify all external URLs mentioned in your post content:
- Set
webmention-scan-content: true. - The action will scan your post's
e-content(IndieWeb microformat) for external links and send webmentions to each unique URL. - This is useful for notifying all sites you referenced in your posts.
Examples:
Example 1: Using Brid.gy endpoint
jobs:
crosspost:
runs-on: ubuntu-latest
steps:
- name: Run action-crosspost with Brid.gy webmentions
uses: tgagor/action-crosspost@v1
with:
dry-run: true
feed-url: https://example.com/rss.xml
webmention-endpoint: https://brid.gy/publish/webmention
webmention-target-hosts: >
https://brid.gy/publish/bluesky
https://brid.gy/publish/mastodonExample 2: Scanning content for links and notifying them
jobs:
crosspost:
runs-on: ubuntu-latest
steps:
- name: Run action-crosspost with content scanning
uses: tgagor/action-crosspost@v1
with:
feed-url: https://example.com/rss.xml
webmention-scan-content: trueFor more on webmentions, see Bridgy Webmentions, Brid.gy Microformats, or IndieWeb Webmention spec.
You can customize the message posted to social networks using the message input.
The following placeholders are supported:
{url}: The post URL.{description}: The meta description from the blog post (if available).{tags}: Tags extracted from the blog post (if available, formatted as hashtags).
Example:
with:
message: |
{description}
{url}
#blog {tags}If the blog post contains a meta description and tags, these will be automatically inserted into the message.
jobs:
crosspost:
runs-on: ubuntu-latest
steps:
- name: Run action-crosspost on a test sitemap
uses: tgagor/action-crosspost@v1
with:
dry-run: true
feed-url: https://example.com/sitemap.xml
since: '1'
since-unit: day
mastodon-access-token: ${{ secrets.MASTODON_ACCESS_TOKEN }}
mastodon-host: mastodon.social
exclude-urls: |
https://example.com/
https://example.com/author*
https://example.com/tags/*
https://example.com/categories/*
https://example.com/posts/*
https://example.com/about/
filter-urls: |
/blog/jobs:
crosspost:
runs-on: ubuntu-latest
steps:
- name: Run action-crosspost on a test RSS feed
uses: tgagor/action-crosspost@v1
with:
dry-run: true
feed-url: https://example.com/index.xml
since: '1'
since-unit: week
twitter-api-key: ${{ secrets.TWITTER_API_KEY }}
twitter-api-secret: ${{ secrets.TWITTER_API_SECRETS }}$
exclude-urls: |
https://example.com/
https://example.com/about/
filter-urls: |
book
message: |
{description}
{url}
#blog {tags}Resulting post message example:
How to automate your blog crossposting with GitHub Actions.
https://example.com/blog/automate-crossposting
#blog #automation #github
-
Sitemaps may mark pages as updated, so posts could appear again even if they were published earlier.
- If you want to avoid reposting updated content, prefer using RSS/Atom feeds, which rely on the original publication date.
-
Always begin with
dry-run: trueto validate your filtering before posting. -
Use
exclude-urlsandfilter-urlsto fine-tune which links should actually be posted.
Crosspost Action is not certified by GitHub. It is provided by a third-party and is governed by separate terms of service, privacy policy, and support documentation.