| description | Fedify provides a ready-to-use relay server implementation for building ActivityPub relay infrastructure. |
|---|
This API is available since Fedify 2.0.0.
Fedify provides the @fedify/relay package for building ActivityPub relay servers—services that forward activities between instances without requiring individual actor-following relationships.
First, install the @fedify/relay package.
::: code-group
deno add @fedify/relaynpm add @fedify/relay @hono/node-serverpnpm add @fedify/relay @hono/node-serveryarn add @fedify/relay @hono/node-serverbun add @fedify/relay:::
Then create a relay using the createRelay() function.
::: code-group
import { createRelay } from "@fedify/relay";
import { MemoryKvStore } from "@fedify/fedify";
const relay = createRelay("mastodon", {
kv: new MemoryKvStore(),
origin: "https://relay.example.com",
name: "My ActivityPub Relay",
subscriptionHandler: async (ctx, actor) => {
// Approve all subscriptions
return true;
},
});
Deno.serve((request) => relay.fetch(request));import "bun";
// ---cut-before---
import { createRelay } from "@fedify/relay";
import { MemoryKvStore } from "@fedify/fedify";
const relay = createRelay("mastodon", {
kv: new MemoryKvStore(),
origin: "https://relay.example.com",
name: "My ActivityPub Relay",
subscriptionHandler: async (ctx, actor) => {
// Approve all subscriptions
return true;
},
});
Bun.serve({
port: 8000,
fetch(request) {
return relay.fetch(request);
},
});import { createRelay } from "@fedify/relay";
import { MemoryKvStore } from "@fedify/fedify";
import { serve } from "@hono/node-server";
const relay = createRelay("mastodon", {
kv: new MemoryKvStore(),
origin: "https://relay.example.com",
name: "My ActivityPub Relay",
subscriptionHandler: async (ctx, actor) => {
// Approve all subscriptions
return true;
},
});
serve({
port: 8000,
fetch(request) {
return relay.fetch(request);
},
});:::
Warning
MemoryKvStore is for development only. For production, use a persistent
store like RedisKvStore from @fedify/redis, PostgresKvStore from
@fedify/postgres, or DenoKvStore from @fedify/denokv.
See the Key–value store section for details.
kv (required)
: A KvStore for storing subscriber information and cryptographic
keys.
origin (required)
: The origin URL where the relay is hosted (e.g., "https://relay.example.com").
name
: Display name for the relay actor. Defaults to "ActivityPub Relay".
queue
: A MessageQueue for background activity processing. Recommended
for production:
~~~~ typescript twoslash
import { createRelay } from "@fedify/relay";
import { MemoryKvStore, InProcessMessageQueue } from "@fedify/fedify";
// ---cut-before---
const relay = createRelay("mastodon", {
kv: new MemoryKvStore(),
origin: "https://relay.example.com",
queue: new InProcessMessageQueue(),
subscriptionHandler: async (ctx, actor) => true,
});
~~~~
> [!NOTE]
> For production, use [`RedisMessageQueue`] or [`PostgresMessageQueue`].
subscriptionHandler (required)
: Callback to approve or reject subscription requests. See
Handling subscriptions. To create an open relay
that accepts all subscriptions:
~~~~ typescript
subscriptionHandler: async (ctx, actor) => true
~~~~
documentLoaderFactory
: A factory function for creating a document loader to fetch remote
ActivityPub objects. See Getting a Federation
object.
authenticatedDocumentLoaderFactory
: A factory function for creating an authenticated document loader.
See
authenticatedDocumentLoaderFactory.
The first parameter to createRelay() specifies the relay protocol.
For detailed protocol specifications, see FEP-ae0c.
| Feature | "mastodon" |
"litepub" |
|---|---|---|
| Activity forwarding | Direct | Wrapped in Announce |
| Following relationship | One-way | Bidirectional |
| Subscription state | Immediate "accepted" |
"pending" → "accepted" |
| Compatibility | Broad (most implementations) | LitePub-aware servers |
Tip
Use "mastodon" for broader compatibility. Switch to "litepub" only if
you need its specific features.
Activities are forwarded directly to subscribers. Instances follow the relay, but the relay doesn't follow back.
import { createRelay } from "@fedify/relay";
import { MemoryKvStore } from "@fedify/fedify";
// ---cut-before---
const relay = createRelay("mastodon", {
kv: new MemoryKvStore(),
origin: "https://relay.example.com",
subscriptionHandler: async (ctx, actor) => true,
});Forwards Create, Update, Delete, Move, and Announce activities.
The relay server follows back instances that subscribe to it. Forwarded
activities are wrapped in Announce objects.
import { createRelay } from "@fedify/relay";
import { MemoryKvStore } from "@fedify/fedify";
// ---cut-before---
const relay = createRelay("litepub", {
kv: new MemoryKvStore(),
origin: "https://relay.example.com",
subscriptionHandler: async (ctx, actor) => true,
});Instance administrators can subscribe to your relay by adding the relay URL in their server settings. The URL format differs depending on the relay type.
The subscription URL differs between Mastodon-style and LitePub-style relays:
| Relay type | Subscription URL | Example |
|---|---|---|
"mastodon" |
Inbox URL: {origin}/inbox |
https://relay.example.com/inbox |
"litepub" |
Actor URL: {origin}/actor |
https://relay.example.com/actor |
For more details on the protocol differences, see FEP-ae0c.
To subscribe from a Mastodon instance:
- Go to Preferences → Administration → Relays
- Click Add new relay
- Enter the relay inbox URL (e.g.,
https://relay.example.com/inbox) - Click Save and enable
The relay will receive a Follow activity from the instance. If the
subscriptionHandler approves the request, the relay sends back an Accept
activity, and the instance becomes a subscriber.
Note
Mastodon only supports Mastodon-style relays. Use the inbox URL
(https://{domain}/inbox) when subscribing from Mastodon.
Pleroma and Akkoma use LitePub-style relays by default. To subscribe:
- Use the admin CLI or MIX task to add the relay
- Enter the relay actor URL (e.g.,
https://relay.example.com/actor)
Consult your server software's documentation for specific instructions. The general process is:
- Find the relay settings in your server's administration panel
- Add the appropriate relay URL (inbox URL for Mastodon-style, actor URL for LitePub-style)
- Wait for the subscription to be approved
The subscriptionHandler is required and determines whether to approve or
reject subscription requests. For an open relay that accepts all subscriptions:
import { createRelay } from "@fedify/relay";
import { MemoryKvStore } from "@fedify/fedify";
// ---cut-before---
const relay = createRelay("mastodon", {
kv: new MemoryKvStore(),
origin: "https://relay.example.com",
subscriptionHandler: async (ctx, actor) => true, // Accept all
});To implement approval logic with blocklists:
import { createRelay } from "@fedify/relay";
import { MemoryKvStore } from "@fedify/fedify";
// ---cut-before---
const blockedDomains = ["spam.example", "blocked.example"];
const relay = createRelay("mastodon", {
kv: new MemoryKvStore(),
origin: "https://relay.example.com",
subscriptionHandler: async (ctx, actor) => {
const domain = new URL(actor.id!).hostname;
if (blockedDomains.includes(domain)) {
return false; // Reject
}
return true; // Approve
},
});The handler receives:
ctx: TheContext<RelayOptions>objectactor: TheActorrequesting subscription
Return true to approve or false to reject. Rejected requests receive a
Reject activity.
The relay provides methods to query and manage followers through the Relay
interface.
Use listFollowers() to iterate over all followers:
import { createRelay } from "@fedify/relay";
import { MemoryKvStore } from "@fedify/fedify";
const relay = createRelay("mastodon", {
kv: new MemoryKvStore(),
origin: "https://relay.example.com",
subscriptionHandler: async (ctx, actor) => true,
});
// ---cut-before---
for await (const follower of relay.listFollowers()) {
console.log(`Follower: ${follower.actorId}`);
console.log(`State: ${follower.state}`);
console.log(`Actor name: ${follower.actor.name}`);
}Use getFollower() to retrieve a specific follower by actor ID:
import { createRelay } from "@fedify/relay";
import { MemoryKvStore } from "@fedify/fedify";
const relay = createRelay("mastodon", {
kv: new MemoryKvStore(),
origin: "https://relay.example.com",
subscriptionHandler: async (ctx, actor) => true,
});
// ---cut-before---
const follower = await relay.getFollower(
"https://mastodon.example.com/users/alice"
);
if (follower != null) {
console.log(`State: ${follower.state}`);
console.log(`Actor: ${follower.actor.preferredUsername}`);
}Each follower entry contains:
actorId: The actor's ID (URL) as a stringactor: The validatedActorobjectstate: Either"pending"or"accepted"
Note
The listFollowers() method requires a KvStore implementation that
supports listing by prefix (Redis, PostgreSQL, SQLite, Deno KV all support
this).
Stored with keys ["follower", actorId]. Actor objects typically range from
1–10 KB. For 1,000 subscribers, expect 1–10 MB of storage.
Two key pairs are generated and stored:
| Key | Purpose |
|---|---|
["keypair", "rsa", "relay"] |
HTTP Signatures |
["keypair", "ed25519", "relay"] |
Linked Data Signatures, Object Integrity Proofs |
Note
These keys are critical for the relay's identity. Back up your KvStore
regularly.
The relay automatically verifies incoming activities using:
Invalid signatures are silently ignored. Enable logging for the
["fedify", "sig"] category to debug verification failures.
Protect against abuse by:
- Implementing a
subscriptionHandlerto validate requests - Maintaining a blocklist
- Rate limiting at the infrastructure level
- Monitoring activity volumes
Warning
Running a relay makes you responsible for forwarded content. Establish clear policies and vet subscribing instances.
The relay has access to all activities that pass through it. Do not store or log activity content beyond operational needs.
Caution
Never forward non-public activities. The relay is designed only for public content distribution.
Enable relay-specific logging:
import { configure, getConsoleSink } from "@logtape/logtape";
await configure({
sinks: { console: getConsoleSink() },
loggers: [
{ category: ["fedify"], lowestLevel: "info", sinks: ["console"] },
],
});Key log categories:
| Category | Description |
|---|---|
["fedify", "federation", "inbox"] |
Incoming activities |
["fedify", "federation", "outbox"] |
Outgoing activities |
["fedify", "sig"] |
Signature verification |
The relay supports OpenTelemetry tracing. Key spans:
| Span | Description |
|---|---|
activitypub.inbox |
Receiving activities |
activitypub.send_activity |
Forwarding activities |
activitypub.dispatch_inbox_listener |
Processing inbox events |