| description | The Federation object is the main entry point of the Fedify library. This section explains the key features of the Federation object. |
|---|
The Federation object is the main entry point of the Fedify library.
It provides a set of methods to configure and run the federated server.
The key features of the Federation object are as follows:
- Registering an actor dispatcher
- Registering inbox listeners
- Registering collections
- Registering object dispatchers
- Creating a
Contextobject - Maintaining a queue of outgoing activities
- Registering a NodeInfo dispatcher
You can create a Federation object by calling createFederation() function
with a configuration object:
import { createFederation, MemoryKvStore } from "@fedify/fedify";
const federation = createFederation<void>({
kv: new MemoryKvStore(),
// Omitted for brevity; see the following sections for details.
});The createFederation() function takes an object with the following
properties. Some of them are required:
Required. The ~FederationOptions.kv property is a KvStore instance
that the Federation object uses to store several kinds of cache data and
to maintain the queue of outgoing activities.
KvStore is an abstract interface that represents a key–value store.
For now, Fedify provides two built-in implementations of KvStore, which are
MemoryKvStore and DenoKvStore classes. The MemoryKvStore class is for
testing and development purposes, and the DenoKvStore class is Deno KV-backed
implementation for production use (as you can guess from the name, it is only
available in Deno runtime).
As separate packages, @fedify/redis provides RedisKvStore class, which
is a Redis-backed implementation for production use, and @fedify/postgres
provides PostgresKvStore class, which is a PostgreSQL-backed implementation
for production use.
Further details are explained in the Key–value store section.
The ~FederationOptions.kvPrefixes property is an object that contains
the key prefixes for the cache data. The following are the key prefixes
that the Federation object uses:
~FederationKvPrefixes.activityIdempotence
: The key prefix used for storing whether activities have already been
processed or not. ["_fedify", "activityIdempotence"] by default.
~FederationKvPrefixes.remoteDocument
: The key prefix used for storing remote JSON-LD documents.
["_fedify", "remoteDocument"] by default.
~FederationKvPrefixes.publicKey
: This API is available since Fedify 0.12.0.
The key prefix used for caching public keys. `["_fedify", "publicKey"]`
by default.
~FederationKvPrefixes.httpMessageSignaturesSpec
: This API is available since Fedify 1.6.0.
The key prefix used for caching HTTP Message Signatures spec. The cached
spec is used to reduce the number of attempts to make signed requests
([double-knocking] technique).
`["_fedify", "httpMessageSignaturesSpec"]` by default.
This API is available since Fedify 0.5.0.
The ~FederationOptions.queue property is a MessageQueue instance that
the Federation object uses to maintain the queue of incoming and outgoing
activities. If you don't provide this option, activities will not be queued
and will be processed immediately.
MessageQueue is an abstract interface that represents a message queue.
For now, Fedify provides two built-in implementations of MessageQueue, which
are InProcessMessageQueue and DenoKvMessageQueue classes.
The InProcessMessageQueue class is for testing and development purposes,
and the DenoKvMessageQueue class is a Deno KV-backed implementation for
production use (as you can guess from the name, it is only available in Deno
runtime).
As separate packages, @fedify/redis provides RedisMessageQueue class,
which is a Redis-backed implementation for production use,
and @fedify/postgres provides PostgresMessageQueue class, which is a
PostgreSQL-backed implementation for production use, and @fedify/amqp
provides AmqpMessageQueue class, which is an AMQP broker-backed
implementation for production use.
Further details are explained in the Message queue section.
Important
While the queue option is optional, it is highly recommended to provide
a message queue implementation in production environments. If you don't
provide a message queue implementation, activities will not be queued and
will be sent immediately. This can make delivery of activities unreliable
and can cause performance issues.
Tip
Since Fedify 1.3.0, you can separately configure the message queue for
incoming and outgoing activities by providing an object with inbox and
outbox properties:
import {
createFederation,
type KvStore,
MemoryKvStore,
type MessageQueue,
} from "@fedify/fedify";
import { PostgresMessageQueue } from "@fedify/postgres";
import { RedisMessageQueue } from "@fedify/redis";
import postgres from "postgres";
import Redis from "ioredis";
createFederation<void>({
kv: null as unknown as KvStore,
// ---cut-before---
queue: {
inbox: new PostgresMessageQueue(
postgres("postgresql://user:pass@localhost/db")
),
outbox: new RedisMessageQueue(() => new Redis()),
}
// ---cut-after---
});Or, you can provide a message queue for only the inbox or outbox by
omitting the other:
import {
createFederation,
type KvStore,
MemoryKvStore,
type MessageQueue,
} from "@fedify/fedify";
import { PostgresMessageQueue } from "@fedify/postgres";
import postgres from "postgres";
createFederation<void>({
kv: null as unknown as KvStore,
// ---cut-before---
queue: {
inbox: new PostgresMessageQueue(
postgres("postgresql://user:pass@localhost/db")
),
// outbox is not provided; outgoing activities will not be queued.
}
// ---cut-after---
});This API is available since Fedify 0.12.0.
Whether to start the task queue manually or automatically.
If true, the task queue will not start automatically and you need to
manually start it by calling the Federation.startQueue() method.
If false, the task queue will start automatically as soon as the first
task is enqueued.
By default, the queue starts automatically.
Tip
This option is useful when you want to separately deploy the web server
and the task queue worker. In this case, you can start the task queue
in the worker process, and the web server process doesn't start the task
queue, but only enqueues tasks. Of course, in this case, you need to
provide a MessageQueue backend that can be shared between the web server
and the worker process (e.g., a Redis-backed message queue) as
the queue option.
This API is available since Fedify 1.4.0.
A factory function that creates a JSON-LD DocumentLoader that the Federation
object uses to load remote JSON-LD documents. The factory function takes
a DocumentLoaderFactoryOptions object and returns a DocumentLoader
function.
Usually, you don't need to set this property because the default document loader is sufficient for most cases. The default document loader caches the loaded documents in the key–value store.
See the
Getting a DocumentLoader section
for details.
This API is available since Fedify 0.4.0.
A factory function that creates an authenticated document loader function. The factory function takes the key pair of an actor and returns a document loader function that loads remote JSON-LD documents as the actor.
Usually, you don't need to set this property because the default document loader factory is sufficient for most cases. The default document loader factory intentionally doesn't cache the loaded documents in the key–value store.
See the Getting an authenticated DocumentLoader
section for details.
This API is available since Fedify 1.4.0.
A factory function to get a JSON-LD context loader that the Federation
object uses to load remote JSON-LD contexts. The type of the factory is
the same as the documentLoaderFactory, but their purposes are different
(see also Document loader vs. context loader
section).
This API is available since Fedify 0.15.0.
Warning
Do not turn on this option in production environments. Disallowing fetching private network addresses is a security feature to prevent SSRF attacks.
Whether to allow fetching private network addresses in the document loader.
Mostly useful for testing purposes.
Turned off by default.
This API is available since Fedify 1.3.0.
The options for making User-Agent header in the HTTP requests that Fedify
makes. By default, it contains the name and version of the Fedify library,
and the name and version of the JavaScript runtime, e.g.:
Fedify/1.3.0 (Deno/2.0.4)
Fedify/1.3.0 (Node.js/v22.10.0)
Fedify/1.3.0 (Bun/1.1.33)
You can customize the User-Agent header by providing options like software
and url. For example, if you provide the following options:
{
software: "MyApp/1.0.0",
url: "https://myinstance.com/"
}The User-Agent header will be like:
MyApp/1.0.0 (Fedify/1.3.0; Deno/2.0.4; +https://myinstance.com/)
Or, you can rather provide a custom User-Agent string directly instead of
an object for options.
Caution
This settings do not affect the User-Agent header of the HTTP requests
that lookupWebFinger(), lookupObject(), and getNodeInfo() functions
make, since they do not depend on the Federation object.
However, Context.lookupObject() method is affected by this settings.
This API is available since Fedify 1.7.0.
The HTTP Signatures specification to use for the first signature attempt when communicating with unknown servers. This option affects the double-knocking mechanism.
When making HTTP requests to servers that haven't been encountered before, Fedify will first attempt to sign the request using the specified signature specification. If the request fails, it will retry with the alternative specification.
Available options are:
"draft-cavage-http-signatures-12"
: HTTP Signatures, which is obsolete but still widely adopted in
the fediverse as of May 2025.
"rfc9421" (default)
: RFC 9421: HTTP Message Signatures, which is the final revision of
the specification and is recommended, but not yet widely adopted
in the fediverse as of May 2025.
Defaults to "rfc9421".
This API is available since Fedify 2.0.0.
HTTP status codes that should be treated as permanent delivery failures.
When an inbox returns one of these codes, the delivery will not be retried
and the permanent failure handler (if registered via
~Federatable.setOutboxPermanentFailureHandler()) will be called.
By default, [404, 410].
// @noErrors: 2345
import { createFederation, InProcessMessageQueue } from "@fedify/fedify";
const federation = createFederation({
// Omitted for brevity; see the related section for details.
queue: new InProcessMessageQueue(),
permanentFailureStatusCodes: [404, 410, 451],
});See also the Permanent delivery failure handling section in the Sending activities page for how to register a handler for permanent delivery failures.
This API is available since Fedify 0.12.0.
The retry policy for sending activities to recipients' inboxes.
By default, this uses an exponential backoff strategy with a maximum of 10 attempts and a maximum delay of 12 hours.
You can fully customize the retry policy by providing a custom function that
satisfies the RetryPolicy type. Or you can adjust the parameters of
the createExponentialBackoffRetryPolicy() function, which is a default
implementation of the retry policy.
Note
This policy is ignored when using message queue backends that provide native
retry mechanisms (MessageQueue.nativeRetrial is true). In such cases,
the backend's native retry logic takes precedence.
This API is available since Fedify 0.12.0.
The retry policy for processing incoming activities.
By default, this uses an exponential backoff strategy with a maximum of 10 attempts and a maximum delay of 12 hours.
In the same way as the outboxRetryPolicy option, you can fully customize
the retry policy by providing a custom function that satisfies the RetryPolicy
type. Or you can adjust the parameters of the built-in
createExponentialBackoffRetryPolicy() function.
Note
This policy is ignored when using message queue backends that provide native
retry mechanisms (MessageQueue.nativeRetrial is true). In such cases,
the backend's native retry logic takes precedence.
This API is available since Fedify 1.4.0.
Activity transformers are a way to adjust activities before sending them to the recipients. It is useful for modifying the activity to fit the recipient's ActivityPub implementation (which may have some quirks) or for adding some additional information to the activity.
See the Activity transformers section for details.
This API is available since Fedify 0.12.0.
Whether the router should be insensitive to trailing slashes in the URL paths.
For example, if this option is true, /foo and /foo/ are treated as the
same path.
Turned off by default.
This API is available since Fedify 1.3.0.
The OpenTelemetry tracer provider that the Federation object uses to
instrument various parts of Fedify for tracing. If omitted, it is configured
to use the default tracer provider (i.e., trace.getTracerProvider()).
For more information, see the OpenTelemetry section.
This API is available since Fedify 1.6.0.
If your federated application is large enough, you may want to defer the
instantiation of the Federation object until all your dispatchers and
listeners are set up. This is useful for structuring your code and
avoiding circular dependencies. Here's a brief instruction on how to
do that:
- Instead of instantiating a
Federationobject usingcreateFederation()function, create aFederationBuilderobject usingcreateFederationBuilder()function. - Register your dispatchers and listeners to the
FederationBuilderobject as you would do with a regularFederationobject. - Call the
FederationBuilder.build()method to create aFederationobject. This method takes the same options ascreateFederation()function, and it returns aFederationobject.
It looks like this:
import { createFederationBuilder } from "@fedify/fedify";
export const builder = createFederationBuilder<void>();
// Register your dispatchers and listeners here...
builder.setActorDispatcher(
"/users/{identifier}",
async (ctx, identifier) => {
// Omitted for brevity
}
);
// ...import { builder } from "./federation.ts";
// Build the `Federation` object
export const federation = await builder.build({
kv: new MemoryKvStore(),
// Omitted for brevity; see the following sections for details.
});If you want to access the Federation object inside dispatchers or listeners
before the FederationBuilder instantiates it, you can use
the Context.federation property. The Context.federation property refers
to the Federation object that is to be instantiated by the FederationBuilder
and is available in the Context object passed to the dispatchers and
listeners. For example, you can access the Federation object like this:
import { type FederationBuilder } from "@fedify/fedify";
import { Person } from "@fedify/vocab";
const builder = null as unknown as FederationBuilder<void>;
// ---cut-before---
builder.setActorDispatcher(
"/users/{identifier}",
async (ctx, identifier) => {
const federation = ctx.federation; // Access the `Federation` object
// Omitted for brevity
// ---cut-start---
return new Person({});
// ---cut-end---
}
);This API is available since Fedify 0.6.0.
The Federation object provides the ~Federation.fetch() method to handle
incoming HTTP requests. The ~Federation.fetch() method takes an incoming
Request and returns a Response.
Actually, this interface is de facto standard in the server-side JavaScript
world, and it is inspired by the window.fetch() method in the browser
environment.
Therefore, you can pass it to the Deno.serve() function in Deno, and
the Bun.serve() function in Bun:
::: code-group
import { type Federation } from "@fedify/fedify";
const federation = null as unknown as Federation<void>;
const request = new Request("");
// ---cut-before---
Deno.serve(
(request) => federation.fetch(request, { contextData: undefined })
);import "bun";
import { type Federation } from "@fedify/fedify";
const federation = null as unknown as Federation<void>;
const request = new Request("");
// ---cut-before---
Bun.serve({
fetch: (request) => federation.fetch(request, { contextData: undefined }),
}):::
However, in case of Node.js, it has no built-in server API that takes
fetch() callback function like Deno or Bun. Instead, you need to use
@hono/node-server package to adapt the ~Federation.fetch() method to
the Node.js' HTTP server API:
::: code-group
npm add @hono/node-server:::
And then, you can use the serve() function from the package:
::: code-group
import { type Federation } from "@fedify/fedify";
const federation = null as unknown as Federation<void>;
const request = new Request("");
// ---cut-before---
import { serve } from "@hono/node-server";
serve({
fetch: (request) => federation.fetch(request, { contextData: undefined }),
}):::
Note
Although a Federation object can be directly passed to the HTTP server
APIs, you would usually integrate it with a web framework. For details,
see the Integration section.
By default, the Federation object recognizes the domain name of the server by
the Host header of the incoming HTTP requests. The Host header is
a standard HTTP header that contains the domain name of the server.
However, the Host header is not always reliable because it can be
bypassed by a reverse proxy or a load balancer. If you use a reverse
proxy or a load balancer, you should configure it to pass the original
Host header to the server.
Or you can make the Federation object recognize the domain name by looking
at the X-Forwarded-Host header instead of the Host header using
the x-forwarded-fetch middleware. To use the x-forwarded-fetch middleware,
install the package:
::: code-group
deno add jsr:@hongminhee/x-forwarded-fetchnpm install x-forwarded-fetchbun add x-forwarded-fetch:::
Then, import the package and place the behindProxy() middleware in front of
the Federation.fetch() method:
::: code-group
// @noErrors: 2300 2307
import { behindProxy } from "x-forwarded-fetch";
import { type Federation } from "@fedify/fedify";
const federation = null as unknown as Federation<void>;
// ---cut-before---
import { behindProxy } from "@hongminhee/x-forwarded-fetch";
Deno.serve(
behindProxy(request => federation.fetch(request, { contextData: undefined }))
);
import { type Federation } from "@fedify/fedify";
const federation = null as unknown as Federation<void>;
// ---cut-before---
import { serve } from "@hono/node-server";
import { behindProxy } from "x-forwarded-fetch";
serve({
fetch: behindProxy((request) => federation.fetch(request, { contextData: undefined })),
});
import "bun";
import { type Federation } from "@fedify/fedify";
const federation = null as unknown as Federation<void>;
// ---cut-before---
import { behindProxy } from "x-forwarded-fetch";
Bun.serve({
fetch: behindProxy((request) => federation.fetch(request, { contextData: undefined })),
});
:::
Tip
When your Federation object is integrated with a web framework, you should
place the behindProxy() middleware in front of the framework's fetch()
method, not the Federation.fetch() method.
This API is available since Fedify 1.5.0.
Or you can explicitly set the canonical origin of the server by passing
the ~FederationOptions.origin option to the createFederation()
function. The ~FederationOptions.origin option is either a string or
a FederationOrigin object, which consists of two fields:
~FederationOrigin.handleHost and ~FederationOrigin.webOrigin.
For example, if you want to set the canonical origin to https://example.com,
you can pass the string:
import { createFederation, type KvStore } from "@fedify/fedify";
// ---cut-before---
const federation = createFederation({
// ---cut-start---
kv: null as unknown as KvStore,
// ---cut-end---
origin: "https://example.com",
});Note
The ~FederationOptions.origin option has to include the leading
https:// or http:// scheme.
Such a configuration leads the constructed URLs using
Context to use the canonical origin
instead of the origin from the incoming HTTP requests, which avoids constructing
unexpected URLs when a request bypasses a reverse proxy or a load balancer.
Caution
For example, suppose that your federated server (upstream) is accessible at
the http://1.2.3.4:8000 and your load balancer (downstream) is accessible at
the https://example.com and forwards the requests to the upstream server.
In this case, you should set the canonical origin to https://example.com to
construct the correct URLs. Otherwise, when some malicious actor directly
sends a request to the upstream server, the constructed URLs will start with
http://1.2.3.4:8000 instead of https://example.com, which can lead to
security issues.
Tip
If your federated server needs to support multiple domains on the same
server, you would not want to set the canonical origin
explicitly. Instead, you should rely on the Host header or
X-Forwarded-Host header to determine the domain name.
This API is available since Fedify 1.5.0.
Sometimes you may want to use different domain names for WebFinger handles
(i.e., fediverse handles) and the server origin. For example, you may want
to use https://ap.example.com/actors/alice as an actor URI but want to use
@alice@example.com as its fediverse handle.
In such cases, you can set the ~FederationOrigin.handleHost different from
the ~FederationOrigin.webOrigin in the ~FederationOptions.origin
option. The ~FederationOrigin.handleHost is used to construct the WebFinger
handles, and the ~FederationOrigin.webOrigin is used to construct the URLs
in the Context object:
import { createFederation, type KvStore } from "@fedify/fedify";
// ---cut-before---
const federation = createFederation({
// ---cut-start---
kv: null as unknown as KvStore,
// ---cut-end---
origin: {
handleHost: "example.com",
webOrigin: "https://ap.example.com",
},
});That is not all. You also need to make the /.well-known/webfinger endpoint
of the ~FederationOrigin.handleHost to redirect to the
/.well-known/webfinger endpoint of the ~FederationOrigin.webOrigin unless
you want to connect the ~FederationOrigin.handleHost to the same server as
the ~FederationOrigin.webOrigin. For example, if your
~FederationOrigin.handleHost is served by Caddy, you can use the following
configuration to redirect the WebFinger requests:
example.com {
redir /.well-known/webfinger https://ap.example.com/.well-known/webfinger
}Or if you use nginx, you can use the following configuration:
server {
server_name example.com;
location /.well-known/webfinger {
return 301 https://ap.example.com$request_uri;
}
}Note
Even if you set the ~FederationOrigin.handleHost different from the
~FederationOrigin.webOrigin, the other fediverse handle with the same
domain name as the ~FederationOrigin.webOrigin will still be recognized.
In the above example, two fediverse handles are recognized as the same:
@alice@example.com@alice@ap.example.com
Federation is designed to be used together with web frameworks. For details,
see the Integration section.
The Federation class is a generic class that takes a type parameter named
TContextData. The TContextData type is the type of the context data,
which is shared among the actor dispatcher, inbox listener, and other
callback functions. The TContextData type can be void if you don't
need to share any context data, but it can be any type if you need to share
context data.
For example, if you want to share a database connection among the actor
dispatcher, inbox listener, and other callback functions, you can set the
TContextData type to the type of the database connection:
import { FreshContext } from "$fresh/server.ts";
import { federation } from "../federation.ts"; // Import the `Federation` object
import { DatabasePool, getPool } from "./database.ts";
export async function handler(request: Request, context: FreshContext) {
return federation.fetch(request, {
contextData: getPool(), // [!code highlight]
onNotFound: context.next.bind(context),
onNotAcceptable: async (request: Request) => {
// Omitted for brevity
}
});
};The Context.data is passed to registered callback functions as their first
parameter within the Context object:
// @noErrors: 2345
import { type Federation } from "@fedify/fedify";
const federation = null as unknown as Federation<void>;
// ---cut-before---
federation.setActorDispatcher("/users/{identifier}", async (ctx, identifier) => {
// There is a database connection in `ctx.data`.
});Another example is to determine the virtual host of the server based on the incoming HTTP request. See the next section for details.
This API is available since Fedify 0.12.0.
Caution
You should not explicitly configure a canonical origin when you want to support multiple domains on the same server.
You may want to support multiple domains on the same server, so-called
virtual hosts. To determine the virtual host of the server based on the
incoming HTTP request, you can use Context.host that contains
the virtual host information:
// @noErrors: 2345
import { type Federation } from "@fedify/fedify";
const federation = null as unknown as Federation<void>;
// ---cut-before---
federation.setActorDispatcher("/@{identifier}", (ctx, identifier) => {
const fullHandle = `${identifier}@${ctx.host}`;
// Omitted for brevity
});
You can access the virtual host information in the actor dispatcher, inbox listener, and other callback functions.
See also the Getting the base URL section in the Context document.