Skip to content

Linting

This package is available since Fedify 2.0.0.

TIP

We highly recommend using the @fedify/lint package in your federated server app to catch common mistakes early and enforce best practices.

Fedify provides the @fedify/lint package, which includes lint rules specifically designed for Fedify applications. It supports Deno Lint, ESLint, and Oxlint, so you can use it regardless of your JavaScript/TypeScript runtime.

The plugin includes rules that check for:

  • Proper actor ID configuration
  • Required actor properties (inbox, outbox, followers, etc.)
  • Correct URL patterns for actor collections
  • Public key and assertion method requirements
  • Collection filtering implementation

Installation

deno add jsr:@fedify/lint
npm add -D @fedify/lint
pnpm add -D @fedify/lint
yarn add -D @fedify/lint
bun add -D @fedify/lint

Deno Lint

Basic setup

Add the plugin to your deno.json configuration file:

{
  "lint": {
    "plugins": ["jsr:@fedify/lint"]
  }
}

By default, this enables all recommended rules.

Custom configuration

You can customize which rules to enable and their severity levels:

{
  "lint": {
    "plugins": ["jsr:@fedify/lint"],
    "rules": {
      "tags": ["recommended"],
      "include": [
        "@fedify/lint/actor-id-required",
        "@fedify/lint/actor-id-mismatch"
      ],
      "exclude": [
        "@fedify/lint/actor-featured-property-required"
      ]
    }
  }
}

Running Deno Lint

After setting up the configuration, run Deno's linter:

deno lint

You can also specify which files to lint:

deno lint federation.ts
deno lint src/federation/

ESLint

Basic setup

Add the plugin to your ESLint configuration file (e.g., eslint.config.ts or eslint.config.js):

import 
fedifyLint
from "@fedify/lint";
// If your `createFederation` code is in `federation.ts` or `federation/**.ts` export default
fedifyLint
;

Or specify your own federation files:

export default {
  ...
fedifyLint
,
files
: ["my-own-federation.ts"],
};

If you use other ESLint configurations:

export default [
  // otherConfig,
  
fedifyLint
,
];

The default configuration applies recommended rules to files that match common federation-related patterns (e.g., federation.ts, federation/*.ts).

Custom configuration

You can customize which files to lint and which rules to enable:

import { 
plugin
} from "@fedify/lint";
export default [{
files
: ["src/federation/**/*.ts"], // Your federation code location
plugins
: {
"@fedify/lint":
plugin
,
},
rules
: {
"@fedify/lint/actor-id-required": "error", "@fedify/lint/actor-id-mismatch": "error", "@fedify/lint/actor-inbox-property-required": "warn", // ... other rules }, }];

Using configurations

The plugin provides two preset configurations:

Enables critical rules as errors and optional rules as warnings:

import 
fedifyLint
from "@fedify/lint";
export default
fedifyLint
;

Strict

Enables all rules as errors:

import { 
plugin
} from "@fedify/lint";
export default [{
files
: ["**/*.ts"],
...
plugin
.
configs
.
strict
,
}];

Running ESLint

Set up your ESLint configuration as shown above and add a script to package.json:

{
  "scripts": {
    "lint": "eslint ."
  }
}

After setting up the configuration, run ESLint on your codebase:

npm run lint
pnpm lint
yarn lint
bun lint

Or run the linter directly via command line:

npx eslint .
pnpx eslint .
yarn eslint .
bunx eslint .

Oxlint

Oxlint is a fast Rust-based linter that supports ESLint-compatible JS plugins. @fedify/lint exposes its rules through Oxlint's JS plugin API via the @fedify/lint/oxlint subpath export.

NOTE

Oxlint's JS plugin API is currently in alpha and not subject to semver.

Basic setup

Add the plugin to your .oxlintrc.json via the jsPlugins field, then enable the rules you want:

{
  "$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json",
  "jsPlugins": ["@fedify/lint/oxlint"],
  "rules": {
    "@fedify/lint/actor-id-required": "error",
    "@fedify/lint/actor-id-mismatch": "error"
  }
}

Rule IDs are namespaced under @fedify/lint/, matching the ESLint preset.

Custom configuration

Each rule accepts "error", "warn", or "off". Enable any subset listed in the Rules section below:

{
  "jsPlugins": ["@fedify/lint/oxlint"],
  "rules": {
    "@fedify/lint/actor-id-required": "error",
    "@fedify/lint/actor-id-mismatch": "error",
    "@fedify/lint/actor-inbox-property-required": "warn",
    "@fedify/lint/actor-outbox-property-required": "warn",
    "@fedify/lint/actor-followers-property-required": "warn",
    "@fedify/lint/actor-public-key-required": "warn",
    "@fedify/lint/actor-assertion-method-required": "warn",
    "@fedify/lint/collection-filtering-not-implemented": "warn"
  }
}

Running Oxlint

Add a script to package.json:

{
  "scripts": {
    "lint": "oxlint ."
  }
}

Then run the linter:

npm run lint
pnpm lint
yarn lint
bun lint

Or invoke Oxlint directly:

npx oxlint .
pnpx oxlint .
yarn oxlint .
bunx oxlint .

Rules

actor-id-required

Ensures all actors have an id property in the actor dispatcher.

When this rule applies: The actor dispatcher returns a Person, Organization, Group, Application, or Service object without an id property.

Why it matters: Every ActivityPub actor must have a unique identifier (ID) to be discoverable and to receive activities from other servers.

// ❌ Bad: Missing id property
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
name
: "John Doe", // No id!
}); }); // ✅ Good: Include id property
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
name
: "John Doe",
}); });

actor-id-mismatch

Validates that actor IDs match the expected URI from Context.getActorUri().

When this rule applies: The id property is set to a value other than ctx.getActorUri(identifier), such as a hardcoded URL string, new URL(...), or a different context method.

Why it matters: Using the wrong URI for the actor ID can cause federation issues. Other servers won't be able to properly verify the actor's identity or send activities to it.

// ❌ Bad: Using hardcoded URL
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
: new
URL
(`https://example.com/users/${
identifier
}`),
name
: "John Doe",
}); }); // ❌ Bad: Using wrong context method
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getFollowersUri
(
identifier
), // Wrong method!
name
: "John Doe",
}); }); // ✅ Good: Use ctx.getActorUri()
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
name
: "John Doe",
}); });

actor-public-key-required

Ensures actors have public keys for HTTP Signatures.

When this rule applies: The actor dispatcher is chained with setKeyPairsDispatcher(), but the actor object doesn't include a publicKey or publicKeys property.

Why it matters: HTTP Signatures are used to verify the authenticity of activities. Without a public key, other servers cannot verify that activities sent by your actor are legitimate.

// ❌ Bad: Missing publicKey when setKeyPairsDispatcher is configured
federation
.
setActorDispatcher
("/users/{identifier}", async (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
name
: "John Doe",
// Missing publicKey! }); }) .
setKeyPairsDispatcher
(async (
ctx
,
identifier
) => {
// Returns key pairs... return []; }); // ✅ Good: Include publicKey from key pairs dispatcher
federation
.
setActorDispatcher
("/users/{identifier}", async (
ctx
,
identifier
) => {
const
keyPairs
= await
ctx
.
getActorKeyPairs
(
identifier
);
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
name
: "John Doe",
publicKey
:
keyPairs
[0].
cryptographicKey
,
}); }) .
setKeyPairsDispatcher
(async (
ctx
,
identifier
) => {
// Returns key pairs... return []; });

actor-assertion-method-required

Validates that actors have assertion methods for Object Integrity Proofs.

When this rule applies: The actor dispatcher is chained with setKeyPairsDispatcher(), but the actor object doesn't include an assertionMethod property.

Why it matters: Object Integrity Proofs use assertion methods to cryptographically sign activities. This provides an additional layer of security beyond HTTP Signatures.

// ❌ Bad: Missing assertionMethod when setKeyPairsDispatcher is configured
federation
.
setActorDispatcher
("/users/{identifier}", async (
ctx
,
identifier
) => {
const
keyPairs
= await
ctx
.
getActorKeyPairs
(
identifier
);
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
name
: "John Doe",
publicKey
:
keyPairs
[0].
cryptographicKey
,
// Missing assertionMethod! }); }) .
setKeyPairsDispatcher
(async (
ctx
,
identifier
) => {
// Returns key pairs... return []; }); // ✅ Good: Include assertionMethod from key pairs dispatcher
federation
.
setActorDispatcher
("/users/{identifier}", async (
ctx
,
identifier
) => {
const
keyPairs
= await
ctx
.
getActorKeyPairs
(
identifier
);
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
name
: "John Doe",
publicKey
:
keyPairs
[0].
cryptographicKey
,
assertionMethod
:
keyPairs
[0].
multikey
,
}); }) .
setKeyPairsDispatcher
(async (
ctx
,
identifier
) => {
// Returns key pairs... return []; });

actor-inbox-property-required

Ensures inbox is defined when setInboxListeners() is configured.

When this rule applies: You've called federation.setInboxListeners() to handle incoming activities, but the actor object doesn't include an inbox property.

Why it matters: The inbox URL tells other servers where to send activities to your actor. Without it, your actor cannot receive follow requests, mentions, or any other activities.

// ❌ Bad: Missing inbox when setInboxListeners is configured
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
name
: "John Doe",
// Missing inbox! }); });
federation
.
setInboxListeners
("/users/{identifier}/inbox", "/inbox");
// ✅ Good: Include inbox property
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
name
: "John Doe",
inbox
:
ctx
.
getInboxUri
(
identifier
),
}); });
federation
.
setInboxListeners
("/users/{identifier}/inbox", "/inbox");

actor-inbox-property-mismatch

Validates that the inbox URI is set using ctx.getInboxUri(identifier).

When this rule applies: The inbox property is set to a value other than ctx.getInboxUri(identifier).

Why it matters: The inbox URI must match the path configured in setInboxListeners(). Using a different URI will cause incoming activities to fail.

// ❌ Bad: Using hardcoded URL
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
inbox
: new
URL
(`https://example.com/inbox/${
identifier
}`), // Wrong!
}); }); // ✅ Good: Use ctx.getInboxUri()
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
inbox
:
ctx
.
getInboxUri
(
identifier
),
}); });

actor-outbox-property-required

Ensures outbox is defined when setOutboxDispatcher() is configured.

When this rule applies: You've called federation.setOutboxDispatcher() to serve the actor's outbox, but the actor object doesn't include an outbox property.

Why it matters: The outbox URL allows other servers and users to view the actor's published activities. It's part of the standard ActivityPub actor profile.

// ❌ Bad: Missing outbox when setOutboxDispatcher is configured
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
// Missing outbox! }); });
federation
.
setOutboxDispatcher
(
"/users/{identifier}/outbox", (
ctx
,
identifier
) => ({
items
: [] })
); // ✅ Good: Include outbox property
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
outbox
:
ctx
.
getOutboxUri
(
identifier
),
}); });

actor-outbox-property-mismatch

Validates that the outbox URI is set using ctx.getOutboxUri(identifier).

When this rule applies: The outbox property is set to a value other than ctx.getOutboxUri(identifier).

Why it matters: The outbox URI must match the path configured in setOutboxDispatcher().

// ❌ Bad: Using wrong context method
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
outbox
:
ctx
.
getInboxUri
(
identifier
), // Wrong method!
}); }); // ✅ Good: Use ctx.getOutboxUri()
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
outbox
:
ctx
.
getOutboxUri
(
identifier
),
}); });

outbox-listener-delivery-required

Warns when an outbox listener body does not deliver the posted activity with ctx.sendActivity() or ctx.forwardActivity().

When this rule applies: You've registered an outbox listener with setOutboxListeners(), but the listener body never calls either delivery method.

Why it matters: Fedify does not federate client-to-server outbox posts automatically. If your application intends to deliver a posted activity, the listener must choose an explicit delivery path.

// ❌ Bad: Listener stores the activity locally but never federates it
federation
.
setOutboxListeners
("/users/{identifier}/outbox")
.
on
(
Activity
, async (
ctx
,
activity
) => {
console
.
log
(
ctx
.
identifier
,
activity
.
id
?.
href
);
}); // ✅ Good: Listener federates explicitly
federation
.
setOutboxListeners
("/users/{identifier}/outbox")
.
on
(
Activity
, async (
ctx
,
activity
) => {
await
ctx
.
sendActivity
(
{
identifier
:
ctx
.
identifier
},
"followers",
activity
,
); }); // ✅ Good: Listener forwards the original posted payload explicitly
federation
.
setOutboxListeners
("/users/{identifier}/outbox")
.
on
(
Activity
, async (
ctx
) => {
await
ctx
.
forwardActivity
(
{
identifier
:
ctx
.
identifier
},
"followers", ); });

actor-followers-property-required

Ensures followers is defined when setFollowersDispatcher() is configured.

When this rule applies: You've called federation.setFollowersDispatcher() to serve the actor's followers collection, but the actor object doesn't include a followers property.

Why it matters: The followers URL allows other servers to discover who follows this actor, which is important for activity delivery and social graph discovery.

// ❌ Bad: Missing followers when setFollowersDispatcher is configured
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
// Missing followers! }); });
federation
.
setFollowersDispatcher
(
"/users/{identifier}/followers", (
ctx
,
identifier
) => ({
items
: [] })
); // ✅ Good: Include followers property
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
followers
:
ctx
.
getFollowersUri
(
identifier
),
}); });

actor-followers-property-mismatch

Validates that the followers URI is set using ctx.getFollowersUri(identifier).

When this rule applies: The followers property is set to a value other than ctx.getFollowersUri(identifier).

Why it matters: The followers URI must match the path configured in setFollowersDispatcher().

// ❌ Bad: Using wrong context method
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
followers
:
ctx
.
getFollowingUri
(
identifier
), // Wrong method!
}); }); // ✅ Good: Use ctx.getFollowersUri()
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
followers
:
ctx
.
getFollowersUri
(
identifier
),
}); });

actor-following-property-required

Ensures following is defined when setFollowingDispatcher() is configured.

When this rule applies: You've called federation.setFollowingDispatcher() to serve the actor's following collection, but the actor object doesn't include a following property.

Why it matters: The following URL allows other servers to discover who this actor follows.

// ❌ Bad: Missing following when setFollowingDispatcher is configured
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
// Missing following! }); });
federation
.
setFollowingDispatcher
(
"/users/{identifier}/following", (
ctx
,
identifier
) => ({
items
: [] })
); // ✅ Good: Include following property
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
following
:
ctx
.
getFollowingUri
(
identifier
),
}); });

actor-following-property-mismatch

Validates that the following URI is set using ctx.getFollowingUri(identifier).

When this rule applies: The following property is set to a value other than ctx.getFollowingUri(identifier).

Why it matters: The following URI must match the path configured in setFollowingDispatcher().

// ❌ Bad: Using wrong context method
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
following
:
ctx
.
getFollowersUri
(
identifier
), // Wrong method!
}); }); // ✅ Good: Use ctx.getFollowingUri()
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
following
:
ctx
.
getFollowingUri
(
identifier
),
}); });

actor-liked-property-required

Ensures liked is defined when setLikedDispatcher() is configured.

When this rule applies: You've called federation.setLikedDispatcher() to serve the actor's liked collection, but the actor object doesn't include a liked property.

Why it matters: The liked URL allows other servers to discover what content this actor has liked.

// ❌ Bad: Missing liked when setLikedDispatcher is configured
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
// Missing liked! }); });
federation
.
setLikedDispatcher
(
"/users/{identifier}/liked", (
ctx
,
identifier
) => ({
items
: [] })
); // ✅ Good: Include liked property
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
liked
:
ctx
.
getLikedUri
(
identifier
),
}); });

actor-liked-property-mismatch

Validates that the liked URI is set using ctx.getLikedUri(identifier).

When this rule applies: The liked property is set to a value other than ctx.getLikedUri(identifier).

Why it matters: The liked URI must match the path configured in setLikedDispatcher().

// ❌ Bad: Using wrong context method
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
liked
:
ctx
.
getFollowersUri
(
identifier
), // Wrong method!
}); }); // ✅ Good: Use ctx.getLikedUri()
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
liked
:
ctx
.
getLikedUri
(
identifier
),
}); });

Ensures featured is defined when setFeaturedDispatcher() is configured.

When this rule applies: You've called federation.setFeaturedDispatcher() to serve the actor's featured/pinned posts collection, but the actor object doesn't include a featured property.

Why it matters: The featured URL allows other servers to discover the actor's pinned or highlighted content (commonly shown at the top of a profile).

// ❌ Bad: Missing featured when setFeaturedDispatcher is configured
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
// Missing featured! }); });
federation
.
setFeaturedDispatcher
(
"/users/{identifier}/featured", (
ctx
,
identifier
) => ({
items
: [] })
); // ✅ Good: Include featured property
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
featured
:
ctx
.
getFeaturedUri
(
identifier
),
}); });

Validates that the featured URI is set using ctx.getFeaturedUri(identifier).

When this rule applies: The featured property is set to a value other than ctx.getFeaturedUri(identifier).

Why it matters: The featured URI must match the path configured in setFeaturedDispatcher().

// ❌ Bad: Using wrong context method
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
featured
:
ctx
.
getFollowersUri
(
identifier
), // Wrong method!
}); }); // ✅ Good: Use ctx.getFeaturedUri()
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
featured
:
ctx
.
getFeaturedUri
(
identifier
),
}); });

Ensures featuredTags is defined when setFeaturedTagsDispatcher() is configured.

When this rule applies: You've called federation.setFeaturedTagsDispatcher() to serve the actor's featured hashtags collection, but the actor object doesn't include a featuredTags property.

Why it matters: The featuredTags URL allows other servers to discover the actor's featured hashtags (commonly used for profile discovery).

// ❌ Bad: Missing featuredTags when setFeaturedTagsDispatcher is configured
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
// Missing featuredTags! }); });
federation
.
setFeaturedTagsDispatcher
(
"/users/{identifier}/tags", (
ctx
,
identifier
) => ({
items
: [] })
); // ✅ Good: Include featuredTags property
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
featuredTags
:
ctx
.
getFeaturedTagsUri
(
identifier
),
}); });

Validates that the featuredTags URI is set using ctx.getFeaturedTagsUri(identifier).

When this rule applies: The featuredTags property is set to a value other than ctx.getFeaturedTagsUri(identifier).

Why it matters: The featuredTags URI must match the path configured in setFeaturedTagsDispatcher().

// ❌ Bad: Using wrong context method
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
featuredTags
:
ctx
.
getFollowersUri
(
identifier
), // Wrong method!
}); }); // ✅ Good: Use ctx.getFeaturedTagsUri()
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
featuredTags
:
ctx
.
getFeaturedTagsUri
(
identifier
),
}); });

actor-shared-inbox-property-required

Ensures endpoints.sharedInbox is defined when setInboxListeners() is configured with a shared inbox path.

When this rule applies: You've called federation.setInboxListeners() with a second parameter (shared inbox path), but the actor object doesn't include an endpoints: new Endpoints({ sharedInbox: ... }) property.

Why it matters: The shared inbox allows other servers to send activities to multiple actors on your server with a single request, improving federation efficiency.

// ❌ Bad: Missing sharedInbox when setInboxListeners has shared inbox path
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
inbox
:
ctx
.
getInboxUri
(
identifier
),
// Missing endpoints.sharedInbox! }); });
federation
.
setInboxListeners
("/users/{identifier}/inbox", "/inbox");
// ✅ Good: Include endpoints.sharedInbox
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
inbox
:
ctx
.
getInboxUri
(
identifier
),
endpoints
: new
Endpoints
({
sharedInbox
:
ctx
.
getInboxUri
(),
}), }); });

actor-shared-inbox-property-mismatch

Validates that endpoints.sharedInbox is set using ctx.getInboxUri() (without identifier).

When this rule applies: The endpoints.sharedInbox property is set to a value other than ctx.getInboxUri() (called without arguments for the shared inbox).

Why it matters: The shared inbox URI must match the shared inbox path configured in setInboxListeners().

// ❌ Bad: Using getInboxUri with identifier for shared inbox
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
inbox
:
ctx
.
getInboxUri
(
identifier
),
endpoints
: new
Endpoints
({
sharedInbox
:
ctx
.
getInboxUri
(
identifier
), // Wrong! Should be no args
}), }); }); // ✅ Good: Use ctx.getInboxUri() without arguments
federation
.
setActorDispatcher
("/users/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
inbox
:
ctx
.
getInboxUri
(
identifier
),
endpoints
: new
Endpoints
({
sharedInbox
:
ctx
.
getInboxUri
(), // No identifier for shared inbox
}), }); });

collection-filtering-not-implemented

Warns when collection dispatchers don't implement filtering.

When this rule applies: The setFollowersDispatcher() callback function has fewer than 4 parameters (missing the filter parameter).

NOTE

Currently, this rule only checks setFollowersDispatcher(). Other collection dispatchers may be added in the future.

Why it matters: Collection filtering allows clients to request specific subsets of a collection, reducing response payload sizes and improving performance. Without filtering, large collections could cause performance issues.

For more information, see the Filtering by server section in the collections manual.

// ❌ Bad: Missing filter parameter
federation
.
setFollowersDispatcher
(
"/users/{identifier}/followers", async (
ctx
,
identifier
,
cursor
) => { // Only 3 parameters!
return {
items
: [] };
} ); // ✅ Good: Include filter parameter (4th parameter)
federation
.
setFollowersDispatcher
(
"/users/{identifier}/followers", async (
ctx
,
identifier
,
cursor
,
filter
) => {
// Use filter to handle filtering requests return {
items
: [] };
} );

Example

Here's an example of code that would trigger lint errors:

// ❌ Wrong: Using relative URL for actor ID
federation
.
setActorDispatcher
(
"/{identifier}", (
_ctx
,
identifier
) => {
return new
Person
({
id
: new
URL
(`/${
identifier
}`), // ❌ Should use ctx.getActorUri()
name
: "Example User",
}); }, );

Corrected version:

// ✅ Correct: Using Context.getActorUri() for actor ID
federation
.
setActorDispatcher
(
"/{identifier}", (
ctx
,
identifier
) => {
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
), // ✅ Correct
name
: "Example User",
inbox
:
ctx
.
getInboxUri
(
identifier
),
outbox
:
ctx
.
getOutboxUri
(
identifier
),
followers
:
ctx
.
getFollowersUri
(
identifier
),
// ... other required properties }); }, );

When you run the linter on the incorrect code, you'll see an error like:

error[fedify-lint/actor-id-mismatch]: Actor's `id` property must match
`ctx.getActorUri(identifier)`. Ensure you're using the correct context method.

See also