| name | fedify-activitypub |
| description | Guide for building and maintaining ActivityPub/fediverse applications using Fedify. This skill should be used when users want to create federated social applications, implement ActivityPub protocols, build new features, test and debug federation, deploy to production, look up FEPs, or maintain existing Fedify applications. |
Fedify ActivityPub Development Guide
This skill provides guidance for building, maintaining, and extending ActivityPub-based federated applications using the Fedify framework.
Documentation Resources
For detailed and up-to-date API documentation:
When answering detailed API questions, fetch the llms-full.txt for comprehensive documentation.
When to Use This Skill
- Creating a new ActivityPub-compatible server application
- Building new features (posts, likes, follows, boosts, replies)
- Setting up actors, inboxes, and collections
- Testing and debugging federation
- Deploying to production environments
- Migrating between Fedify versions
- Looking up FEPs (Fediverse Enhancement Proposals)
Quick Start
deno install -A jsr:@fedify/cli
fedify init my-app
Core Architecture
Federation Instance
import { createFederation, MemoryKvStore, InProcessMessageQueue } from "@fedify/fedify";
const federation = createFederation<AppContext>({
kv: new MemoryKvStore(),
queue: new InProcessMessageQueue(),
});
Production stores: @fedify/redis, @fedify/postgres, DenoKvStore
Actor Dispatcher (Required)
federation.setActorDispatcher("/users/{identifier}", async (ctx, identifier) => {
const user = await db.getUserById(identifier);
if (!user) return null;
return new Person({
id: ctx.getActorUri(identifier),
preferredUsername: user.username,
inbox: ctx.getInboxUri(identifier),
publicKey: (await ctx.getActorKeyPairs(identifier))[0].cryptographicKey,
});
});
Key Pairs (Required for sending)
federation.setKeyPairsDispatcher(async (ctx, identifier) => [{
publicKey: await importJwk(user.publicKeyJwk, "public"),
privateKey: await importJwk(user.privateKeyJwk, "private"),
}]);
Inbox Listeners
federation
.setInboxListeners("/users/{identifier}/inbox", "/inbox")
.on(Follow, handleFollow)
.on(Create, handleCreate)
.on(Like, handleLike)
.on(Undo, handleUndo);
Building Features
Implementing Follow/Unfollow
.on(Follow, async (ctx, follow) => {
const follower = await follow.getActor();
const followeeId = ctx.recipient;
await db.createFollow(follower.id.href, followeeId);
await ctx.sendActivity(
{ identifier: followeeId },
follower,
new Accept({ actor: ctx.getActorUri(followeeId), object: follow })
);
})
.on(Undo, async (ctx, undo) => {
const object = await undo.getObject();
if (object instanceof Follow) {
const follower = await undo.getActor();
await db.removeFollow(follower.id.href, ctx.recipient);
}
});
Creating and Sending Posts
async function createPost(ctx: Context, authorId: string, content: string) {
const post = await db.createPost(authorId, content);
const note = new Note({
id: ctx.getObjectUri(Note, { id: post.id }),
attributedTo: ctx.getActorUri(authorId),
content: post.content,
published: Temporal.Now.instant(),
to: new URL("https://www.w3.org/ns/activitystreams#Public"),
});
await ctx.sendActivity(
{ identifier: authorId },
"followers",
new Create({ actor: ctx.getActorUri(authorId), object: note })
);
}
Implementing Likes
await ctx.sendActivity(
{ identifier: "alice" },
targetActor,
new Like({
actor: ctx.getActorUri("alice"),
object: new URL("https://remote.example/posts/123"),
})
);
.on(Like, async (ctx, like) => {
const objectUri = like.objectId;
const liker = await like.getActor();
await db.addLike(objectUri.href, liker.id.href);
});
Object Dispatchers
Expose posts and other objects:
federation.setObjectDispatcher(Note, "/posts/{id}", async (ctx, { id }) => {
const post = await db.getPost(id);
if (!post) return null;
return new Note({
id: ctx.getObjectUri(Note, { id }),
attributedTo: ctx.getActorUri(post.authorId),
content: post.content,
published: Temporal.Instant.from(post.createdAt),
});
});
Collections with Pagination
federation.setFollowersDispatcher("/users/{identifier}/followers",
async (ctx, identifier, cursor) => {
if (cursor == null) return null;
const { items, nextCursor } = await db.getFollowers(identifier, cursor, 20);
return {
items: items.map(f => new URL(f.actorUri)),
nextCursor,
};
}
)
.setFirstCursor(async () => "")
.setCounter(async (ctx, id) => db.countFollowers(id));
Testing
Mock Federation (@fedify/testing)
import { createFederation } from "@fedify/testing";
const federation = createFederation({ contextData: { userId: "test" } });
await federation.receiveActivity(followActivity);
console.log(federation.sentActivities);
federation.reset();
CLI Testing Tools
fedify inbox
fedify tunnel 3000
fedify lookup @user@host
fedify webfinger user@host
Enable Private Network (Testing Only)
const federation = createFederation({
allowPrivateAddress: true,
});
Logging & Debugging
import { configure, getConsoleSink } from "@logtape/logtape";
await configure({
sinks: { console: getConsoleSink() },
filters: {},
loggers: [
{ category: ["fedify"], sinks: ["console"], lowestLevel: "debug" },
],
});
Key log categories:
["fedify", "federation", "inbox"] - Incoming activities
["fedify", "federation", "outbox"] - Outgoing activities
["fedify", "sig", "http"] - HTTP signature verification
Framework Integration
| Framework | Package | Integration |
|---|
| Hono | @fedify/hono | app.use(federation()) |
| Express | @fedify/express | integrateFederation() |
| Fastify | @fedify/fastify | fedifyPlugin |
| Next.js | @fedify/next | fedifyWith() in middleware |
| SvelteKit | @fedify/sveltekit | fedifyHook() |
| NestJS | @fedify/nestjs | Middleware |
Production Deployment
Key considerations:
- Use production KV store (
@fedify/redis, @fedify/postgres, DenoKvStore)
- Use persistent message queue for activity delivery
- Store key pairs in database (never regenerate on restart)
- Separate web and queue workers with
manuallyStartQueue: true
- Set up logging with appropriate sinks
- Configure
origin option for proper URL generation
FEP Resources
FEPs (Fediverse Enhancement Proposals) define interoperability standards:
Look up FEP: https://codeberg.org/fediverse/fep/src/branch/main/fep/{id}/fep-{id}.md
Important FEPs: FEP-8b32 (Object Integrity Proofs), FEP-fe34 (Origin security), FEP-1b12 (Groups)
Detailed Reference
For comprehensive API documentation, advanced patterns, and migration guides, see references/fedify-manual.md.