Claude-skill-registry fedify-activitypub
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.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/fedify-activitypub" ~/.claude/skills/majiayu000-claude-skill-registry-fedify-activitypub && rm -rf "$T"
skills/data/fedify-activitypub/SKILL.mdFedify 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:
- LLM-optimized docs: https://fedify.dev/llms.txt (overview) and https://fedify.dev/llms-full.txt (complete)
- Official docs: https://fedify.dev
- API Reference: https://jsr.io/@fedify/fedify
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
# Install CLI deno install -A jsr:@fedify/cli # or: npm install -g @fedify/cli # Create new project (interactive) fedify init my-app
Core Architecture
Federation Instance
import { createFederation, MemoryKvStore, InProcessMessageQueue } from "@fedify/fedify"; const federation = createFederation<AppContext>({ kv: new MemoryKvStore(), // Required: cache and state queue: new InProcessMessageQueue(), // Recommended: async activity processing });
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
// Handle incoming Follow .on(Follow, async (ctx, follow) => { const follower = await follow.getActor(); const followeeId = ctx.recipient; await db.createFollow(follower.id.href, followeeId); // Send Accept back await ctx.sendActivity( { identifier: followeeId }, follower, new Accept({ actor: ctx.getActorUri(followeeId), object: follow }) ); }) // Handle Undo (unfollow) .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", // Send to all followers new Create({ actor: ctx.getActorUri(authorId), object: note }) ); }
Implementing Likes
// Send a Like await ctx.sendActivity( { identifier: "alice" }, targetActor, new Like({ actor: ctx.getActorUri("alice"), object: new URL("https://remote.example/posts/123"), }) ); // Handle incoming Like .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" } }); // Test inbox handlers await federation.receiveActivity(followActivity); // Inspect sent activities console.log(federation.sentActivities); // Reset between tests federation.reset();
CLI Testing Tools
fedify inbox # Ephemeral server to receive activities fedify tunnel 3000 # Expose local server with HTTPS fedify lookup @user@host # Debug actor resolution fedify webfinger user@host
Enable Private Network (Testing Only)
const federation = createFederation({ allowPrivateAddress: true, // SSRF risk - disable in production });
Logging & Debugging
import { configure, getConsoleSink } from "@logtape/logtape"; await configure({ sinks: { console: getConsoleSink() }, filters: {}, loggers: [ { category: ["fedify"], sinks: ["console"], lowestLevel: "debug" }, ], });
Key log categories:
- Incoming activities["fedify", "federation", "inbox"]
- Outgoing activities["fedify", "federation", "outbox"]
- HTTP signature verification["fedify", "sig", "http"]
Framework Integration
| Framework | Package | Integration |
|---|---|---|
| Hono | | |
| Express | | |
| Fastify | | |
| Next.js | | in middleware |
| SvelteKit | | |
| 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
option for proper URL generationorigin
FEP Resources
FEPs (Fediverse Enhancement Proposals) define interoperability standards:
- Repository: https://codeberg.org/fediverse/fep
- Discussion: https://socialhub.activitypub.rocks/c/standards/fep/54
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.