Released on October 1, 2026.
@fedify/fedify
-
Added
onRequestFinished()to observe every inbox delivery, including rejected requests and preparation errors, with signature/proof checks, actual verification keys, the final authentication decision, and the processing outcome. The callback is awaited independently of trace sampling, and its errors do not change delivery results. [#1191, #1201] -
Added support for FEP-ef61 portable objects, whose IDs are
ap:orap+ef61:URIs with a DID instead of a host, e.g.,ap://did:key:z6Mk.../actor. A Fedify server can now act as a gateway for portable actors: it serves their actor documents, objects, collections, and hashlink media, accepts deliveries to their inboxes, and sends their activities. It can also consume the portable objects of others. The new Portable objects chapter of the manual walks through running portable actors, and describes the FEP-ef61 profile that Fedify supports, including where it deliberately differs from the FEP and what it does not implement. Applications without portable actors are unaffected, except that their dispatchers may be called for requests to the gateway endpoint, /.well-known/apgateway/, and thatContext.sendActivity()may reject activities that embed others' portable objects (see below). [#288, #1151, #1198]-
Added
verifyPortableObjectProof(), which enforces the FEP-ef61 proof policy: a portable actor, activity, or object needs an FEP-8b32 Object Integrity Proof whoseverificationMethodis a DID URL of the DID in its ID, and a portable actor needs a non-emptygatewayslist of HTTP(S) origins. A document whose ID is a compatible identifier, e.g.,https://gw.example/.well-known/apgateway/did:key:z6Mk.../actor, is verified against the DID in it. Its detailed result distinguishes documents outside the policy, unsecured collections, missing or invalid proofs, invalid gateways, unsupported verification methods, and DID mismatches. [#832, #968, #1093, #1105, #1148, #1178] -
Added
verifyPortableObject(), which verifies proofs likeverifyPortableObjectProof(), and also accepts an actor'sinbox,outbox,followers,following, orlikedcollection without a proof if a gateway that the actor lists served it, as FEP-ef61 allows. TheVerifyPortableObjectOptions,VerifyPortableObjectResult, andVerifyPortableObjectFailureReasontypes describe it. [#836, #1084, #1093, #1105] -
Added the
Context.verifyPortableObjectproperty, which isverifyPortableObject()with the context's loaders. Since it has the same name as the option of property accessors, passing a context as their options, e.g.,await create.getObject(ctx), verifies portable objects. Inboxes use it as the default verifier of received activities, as doContext.lookupObject(),Context.traverseCollection(), and theonOutboxErrorcallback. Added theverifyPortableObjectoption toVerifyObjectOptions. [#1107, #1120] -
verifyProof()now resolves Ed25519did:keyverification methods, e.g.,did:key:z6Mk...#z6Mk..., locally without fetching them, andverifyObject()accepts a proof made by a DID as authenticating an actor or attribution whose portable ID or compatible identifier has that DID. [#827, #829, #915, #926, #1093, #1105] -
Added the
Context.getPortableActorUri(),Context.getPortableObjectUri(),Context.getPortableInboxUri(),Context.getPortableOutboxUri(),Context.getPortableFollowingUri(),Context.getPortableFollowersUri(),Context.getPortableLikedUri(),Context.getPortableFeaturedUri(),Context.getPortableFeaturedTagsUri(), andContext.getPortableCollectionUri()methods, which build portable IDs from the paths of the corresponding dispatchers and a DID. Custom implementations of theContextinterface need to implement them. [#835, #839, #841, #1076, #1092, #1111, #1114, #1142] They throw aTypeErrorfor adid:keyDID that is not encoded in base58-btc, as FEP-ef61 requires, or for an identifier that has a.or..path segment. [#841, #1114, #1154, #1186] -
Added the
portableoption toContext.parseUri()and theParseUriOptionsinterface. With{ portable: true }, the method also recognizes portable IDs and their compatible identifiers, and the result has the DID in its newauthorityproperty. [#1143, #1145] -
Object dispatchers and the actor dispatcher now serve portable objects and actors through the gateway endpoint, e.g.,
GET /.well-known/apgateway/did:key:z6Mk.../notes/123, with the path after the DID. An object is served only if its ID canonically equals the requested portable ID and it has a proof made with a key of the DID. A non-public object is served only if the dispatcher has an authorization predicate, as FEP-ef61 forbids gateways to serve it to anyone but its audience. Signed tombstones are served with410 Gone. Added theRequestContext.portableRequestproperty, which tells the dispatcher the requested DID and ID. [#835, #841, #1076, #1113, #1114, #1124, #1153, #1183] -
Added the
RequestContext.isSignedByAudience()method and theIsSignedByAudienceOptionsinterface, which check whether a request is signed by an actor in the audience of an object, e.g., in the authorization predicate of an object dispatcher. Custom implementations of theRequestContextinterface need to implement the method. [#1153, #1183] -
Collection dispatchers now serve the collections of portable actors through the gateway endpoint, e.g.,
GET /.well-known/apgateway/did:key:z6Mk.../users/alice/outbox, if the actor is a portable actor under the requested DID whose corresponding property refers to the collection. A collection that is not paginated always hastotalItems, so that consumers can tell an empty one from other objects. Added theCustomCollectionCallbackSetters.mapPortableOwner()method and thePortableCollectionOwnerMappertype, which tie a custom collection to its portable owner. [#1111, #1142] -
Added
Federatable.setHashlinkMediaDispatcher()and theHashlinkMediaRequestinterface, which serve resources that portable objects refer to with SHA-256 hashlinks, e.g.,GET /.well-known/apgateway/hl:zQm.... Fedify does not verify the dispatcher's response against the digest. Addedhashlink_mediato the values of thefedify.endpointmetric attribute. [#838, #1080] -
Inbox listeners now accept deliveries to portable inboxes through the gateway endpoint, e.g.,
POST /.well-known/apgateway/did:key:z6Mk.../users/alice/inbox, if the actor dispatcher returns a portable actor whoseinboxis the requested inbox and whosegatewaysinclude this server. [#839, #1092] -
Fedify now forwards an activity delivered to a portable inbox to the actor's other gateways at most once, as FEP-ef61 recommends, if the activity is authenticated by its own proof or Linked Data Signature and the
KvStoresupportscas(). Forwarded requests are signed with this server's RSA gateway key for the actor if the key pairs dispatcher returns one, and are sent unsigned otherwise. An activity is not forwarded back to the gateway that forwarded it if Fedify can identify that gateway by its gateway key signature on the delivery. Added theFederationOptions.portableInboxForwardingandFederationKvPrefixes.portableInboxForwardingoptions. [#839, #1092, #1100, #1101, #1104, #1110] -
Context.sendActivity()andInboxContext.forwardActivity()now deliver to portable inboxes through the recipient's gateways, or else the@gatewaylocation hints of the inbox, trying at most five of them until one accepts the activity. Previously, such a delivery failed withUrlError: Unsupported protocol: ap+ef61:. Queue workers of older Fedify versions deliver such queued activities only through the first gateway, so upgrade them before the servers that enqueue deliveries. [#1147, #1180] -
Added the
ActorCallbackSetters.mapPortableActorId()method and thePortableActorIdMappertype. For an actor that the callback maps to a portable ID,Context.getActorKeyPairs()identifies its key pairs by the actor's compatible identifier on this server, e.g.,https://example.com/.well-known/apgateway/did:key:z6Mk.../users/alice#main-key, so that they serve as this server's gateway keys for the actor, which sign HTTP requests made on behalf of the actor, but never make Object Integrity Proofs or Linked Data Signatures. [#840, #1099] -
HTTP Signature verification now accepts a gateway key of a portable actor if the actor document at the key ID has a valid proof by the actor's DID, embeds the key in its
assertionMethod, or else in itspublicKey, and lists the gateway in itsgateways. Such a key is owned by the portable actor, soRequestContext.getSignedKeyOwner(),getKeyOwner(), anddoesActorOwnKey()return or match the actor. Keys at compatible identifiers are cached apart for each purpose, for an hour at most. [#840, #1095, #1099, #1105, #1119, #1123, #1164] A key at anap:key ID is accepted likewise if an actor document fetched through the key ID's location hints vouches for it. [#1096, #1132, #1134, #1165] -
Inboxes accept an activity of a portable actor, or an activity with a portable ID, only if it has a valid proof made by the DID of that ID; an HTTP Signature or a Linked Data Signature does not authenticate it, and such an activity is rejected with
401 Unauthorized. [#840, #1099] -
Inboxes independently verify each portable actor, activity, and object embedded in a compound document, e.g., the
Notein aCreate, against its own proof before dispatch, following Fedify's interim map-local profile, since neither FEP-8b32 nor Verifiable Credential Data Integrity defines the boundaries of embedded proofs yet. A valid outer proof does not authenticate an unsigned or invalid embedded portable object. Documents with proof sets, or that exceed the traversal limits, are rejected. A key embedded in a verified portable actor needs no proof of its own if the key's ID is the actor's ID plus a fragment. This profile may change in Fedify 3.0. [#938, #1041, #1094, #1102, #1133, #1138] -
Context.sendActivity()gives an activity that contains portable objects at most one proof: an activity that already has one is sent as is, and a portable activity is signed only by the key whose ID is a DID URL for its DID. An activity of a portable actor has to have a portable ID or a compatible identifier of the actor's DID, and never gets a Linked Data Signature. Otherwise,sendActivity()rejects with aTypeErrorbefore anything is delivered or queued. So does a non-portable activity that embeds portable objects, including ones with compatible identifiers, when several Ed25519 keys are available, and an activity with portable objects in which any map carries a proof set, or an embedded map carries a proof but not its own@context, e.g., a received signedFollowembedded in anAccept, which Fedify inboxes would reject; refer to such an object by its ID instead. Sign portable activities withsignObject()beforehand, or pass the DID's key as an explicit sender key. [#840, #1041, #1045, #1073, #1099] -
Your portable actors and objects may have compatible identifiers as their IDs, e.g.,
https://example.com/.well-known/apgateway/did:key:z6Mk.../actor, for interoperability with software that cannot handle portable IDs. Fedify treats them as portable wherever it serves or sends them, and warns if such an ID is malformed or is not on the owner's first gateway, as FEP-ef61 requires. Build them withtoCompatibleEf61Id()before signing the documents. [#1106, #1109, #1155, #1188] -
The WebFinger endpoint now serves portable actors: the domain of the actor's address comes from the first gateway in its
gateways, and itsselflink is its compatible identifier on that gateway. WebFinger resources can be portable IDs, which are passed tomapAlias().Context.lookupObject()resolves the handles of portable actors on other servers. [#837, #1082, #1106, #1109] -
Fedify logs a warning if an actor dispatcher returns a portable actor whose
gatewaysare invalid, or whose collections are not the portable IDs or compatible identifiers that the correspondingContext.getPortable*Uri()methods build, instead of warning that a portable actor's ID or collections do not match the URIs thatContext.getActorUri()and the like build. [#837, #1082, #1111, #1142, #1148, #1178] -
Context.routeActivity()routes a portable activity that has no valid proof only if all of its actors have the same DID as the activity, comparing FEP-fe34 origins, where the origin of a portable ID or a compatible identifier is its DID. [#1146, #1171] -
Outbox listeners compare the
actorof a posted activity with a portable outbox owner by their canonical portable IDs, so that the actor can be referred to with@gatewaylocation hints, another URI scheme, or as a compatible identifier on another gateway. [#1159, #1189]
-
-
Changed Fedify to treat documents whose IDs are FEP-ef61 compatible identifiers, such as
https://gw.example/.well-known/apgateway/did:key:z6Mk.../actor, as the portable objects they stand for, as FEP-ef61 requires, instead of trusting them by the web origin that served them. Previously, any server could act ashttps://evil.example/.well-known/apgateway/did:key:z6MkAlice/actorwithout a proof by Alice's DID. Some implementations, e.g., tootik, identify their portable actors this way. [#288, #1093, #1105]-
Inboxes now reject an activity of an actor whose ID is a compatible identifier with
401 Unauthorizedunless it has a valid Object Integrity Proof made by the actor's DID. Activities of actors that publish compatible identifiers without signing them, or whose DIDs use key types Fedify does not support, are no longer accepted. -
verifyObject()never authenticates an attribution or actor whose ID is a compatible identifier by a key at an ordinary URL. -
HTTP Signature verification rejects a key at a compatible identifier unless the actor document has a valid proof by the actor's DID and vouches for the key. A key at an ordinary URL that claims a portable actor as its owner or controller is no longer that actor's key, and
getKeyOwner()anddoesActorOwnKey()no longer resolve portable actors by web origin.
-
-
Changed property accessors to verify the FEP-ef61 portable objects that they dereference when a
Contextis passed as their options, e.g.,await create.getObject(ctx), since the newContext.verifyPortableObjectproperty has the same name as theirverifyPortableObjectoption. This also applies to objects with ordinary HTTP(S) IDs: their references to compatible identifiers are now verified as portable objects instead of being trusted because of their origin. In inbox listeners, accessors do so even without options. [#288, #1107, #1120] -
Fixed
signObject()so that a signed object keeps verifying after it is assigned to a typed parent and the parent is serialized.signObject()now captures the secured JSON document its proof covers, and nested serialization embeds that document verbatim instead of rebuilding the child under the parent's JSON-LD context. Producing a compound document, such as a signedNotein a signed FEP-ef61 portableCreate, withsignObject()no longer requires assembling the JSON by hand. [#288, #1041, #1044, #1051]- The captured document is a snapshot:
clone()does not carry it, and mutating a signed object in place does not change it. Sign a clone again when it has to be embedded as a secured child. - Serialization falls back to the previous behavior for objects parsed with
fromJsonLd(), objects that already carried a proof, andtoJsonLd()calls whosecontextoption could hide the internal placeholder. - Outgoing JSON-LD compatibility normalization now leaves a nested self-contained secured document untouched while signing and sending, so it cannot rewrite bytes that the child's own proof covers. Inbound verification is unaffected.
- Fanout delivery now reuses the document the activity was already serialized into instead of reparsing and reserializing it, which previously invalidated an embedded signed child and the outer proof that covered it.
- The captured document is a snapshot:
-
Allowed object dispatchers to return
Tombstonefor deleted objects, as actor dispatchers already can. Fedify now serves such object URIs with410 Goneand the serialized tombstone body, as ActivityPub recommends, after applying the authorization predicate as for other objects, so that applications no longer need a separate route in front of Fedify for deleted posts. Fedify logs a warning if the tombstone'siddoes not matchContext.getObjectUri(). [#1112, #1117]- Changed the return type of
ObjectDispatcherfromTObject | nulltoTObject | Tombstone | null. - Added a
RequestContext.getObject()overload that takes aGetObjectOptionsobject. By default,getObject()still returnsnullfor a tombstone unless the tombstone is an instance of the requested class, e.g.,ObjectorTombstone. Pass{ tombstone: "passthrough" }to receive tombstones. - Added the
GetObjectOptionsinterface. - Changed object dispatchers registered for
TombstoneorObjectthat return tombstones to be served with410 Goneinstead of200 OK.
- Changed the return type of
-
Changed HTTP Signature verification to verify at most the first three RFC 9421 signatures of a request, in the order of its
Signature-Inputheader, and to ignore the rest. Each signature may make Fedify fetch the key that it names from a URL of the sender's choosing, so a single unauthenticated request with many signatures could make Fedify fetch any number of URLs. A signature counts even if it fails before its key is fetched, and a key is now looked up only once for all the signatures of a request that name it. A request whose only valid signature comes after the first three is no longer accepted; senders usually put a single signature on a request. [#1130, #1166]-
Added the
FederationOptions.maxHttpSignaturesoption to change the limit for the inbox andRequestContext.getSignedKey(), and themaxSignaturesoption ofverifyRequest()andverifyRequestDetailed()for verifying requests directly. Both have to be positive integers orInfinity, which turns the limit off; other values throw aRangeError. -
When a cached key does not verify a signature, Fedify now fetches it again for that signature only, instead of verifying every signature of the request once more without the key cache.
-
-
Changed cached actor public keys and remembered per-origin HTTP Message Signatures specs to expire, so a
KvStorethat never sees an explicit clear no longer accumulates entries for actors and origins that have stopped federating. Keys expire after 30 days and specs after 90 days by default, and both windows are configurable through the newFederationOptions.publicKeyTtlandFederationOptions.httpMessageSignaturesSpecTtloptions. [#1017, #1027 by Heewon Chae]- Shortening a window trades storage for remote requests: an expired key has to be refetched before the next signature verification, and an expired spec has to be relearned by double-knocking on the next delivery. Refetching fails while the peer is unavailable, so a very short window makes verification depend on the peer being reachable.
- Entries written by earlier versions of Fedify have no expiry and are left as they are; they gain one the next time they are written. See the new Clearing legacy cache entries section of the key–value store guide to clear them proactively instead of waiting.
-
Changed the built-in document loader, context loader, and authenticated document loader to time out each call after 10 seconds by default. Previously, a remote server that responded slowly or never could hold a request for as long as the runtime and the network allowed, e.g., an incoming activity whose signature key Fedify fetched, and the sender picks such key URLs. The time limit covers the whole call, including every redirect and alternate document it follows, double-knocking retries, and reading the response body. A call that times out throws a
FetchErrorwithout a response, whosecauseis aDOMExceptionnamed"TimeoutError", so a key fetch that times out is reported as akeyFetchErrorbyverifyRequestDetailed()and is cached like other failures to fetch a key. Custom document loaders are not affected. [#1131, #1169]-
Added
FederationOptions.documentLoaderTimeoutoption to change the time limit, or to turn it off withnull. -
Added the
timeoutoption togetAuthenticatedDocumentLoader().
-
-
Changed the built-in document loaders to read the body of an error response before they throw a
FetchError, at most 1 MiB, so that the time limit also bounds reading it.FetchError.responsekeeps the body byte for byte, or only the status and headers if the body is larger than that. [#1131, #1169] -
Fixed outbound delivery circuit breaker transitions when a key–value store compares encoded values. Existing half-open states can now recover after switching to a CAS-capable store. [#1163, #1167]
-
Fixed
verifyProof()so Ed25519 JCS proofs authenticate every received proof option exceptproofValue, includingexpires,domain,challenge,nonce, and extension options. It now rejects expired or malformed proof options, and callers can provide expecteddomainandchallengevalues throughVerifyProofOptionsto prevent cross-domain or replay use. [#968] -
Added support for the ActivityPub Media Upload extension so that servers can accept client-to-server media uploads: [#754, #927]
FederationandFederationBuildergained asetMediaUploader()method (through the newMediaUploaderSettersinterface) that registers amultipart/form-dataupload endpoint. Its callback finalizes the uploadedfilealongside the postedobjectshell and returns either the created object (201 Created) or theURLat which it will become available once processing finishes (202 Accepted).Contextgained agetMediaUploaderUri()method for building the endpoint URI, which actor dispatchers advertise under the newEndpoints.uploadMediaproperty.- A new metric endpoint category,
media_upload, classifies these requests in thefedify.endpointattribute. - Fedify logs a runtime warning when a callback's returned URI does not point at a registered object dispatcher route, when a registered media uploader is not advertised under
endpoints.uploadMedia, or when a media uploader is registered without anauthorize()hook (so the endpoint would accept uploads from anyone).
-
Added a custom background task API that generalizes Fedify's enqueue-and-process-later pattern to arbitrary application-defined jobs:
FederationandFederationBuildergained adefineTask()method through the newTaskRegistryinterface, whichFederatablenow extends.ContextgainedenqueueTask()andenqueueTaskMany()methods, withdelayandorderingKeyoptions (newTaskEnqueueOptionsinterface).- Every task requires a Standard Schema (
schemaoption) from which the payload type is inferred; payloads are validated at enqueue time (fail fast) and again at dequeue time (protection against schema drift across deployments). - Payloads are serialized by Fedify with devalue, so
Date,Map,Set,URL,bigint, circular references, and Activity Vocabulary objects round-trip faithfully across every message queue backend. - Failed handlers are retried with exponential backoff by default; tasks support per-task
retryPolicyandonErroroptions, the newFederationOptions.taskRetryPolicysets the federation-wide default, and queues withnativeRetrialdelegate retries to the backend. - Tasks can be isolated from activity delivery through the new
FederationQueueOptions.taskslot or a per-taskqueueoption; without them, tasks fall back to the outbox queue unless the newFederationOptions.taskQueueResolutionoption is set to"strict".Federation.startQueue()now acceptsqueue: "task"to run a task-only worker. - Tasks can request at-most-once enqueue with a
deduplicationKey(newTaskEnqueueOptions.deduplicationKey). A queue declaring the newMessageQueue.nativeDeduplicationcapability owns the check and receives the key through the newMessageQueueEnqueueOptions.deduplicationKey; otherwise Fedify performs a best-effort key–value guard through the optionalKvStore.casprimitive, under a newtaskDeduplicationkey prefix. The marker TTL and the no-casfallback are tunable with the newFederationOptions.taskDeduplicationTtlandFederationOptions.taskDeduplicationFallbackoptions. [#206, #797, #798, #799, #803, #806, #812, #923 by ChanHaeng Lee]
-
Added
MessageQueue.atomicEnqueueManyfor queues that implementenqueueMany()with separate sends. Fedify still uses their batch path normally, but rejects a multi-message batch governed by onededuplicationKeybefore a partial send can undermine deduplication. [#930, #934] -
Fixed CommonJS distribution files that use Temporal so they no longer require
@js-temporal/polyfillat runtime. The CommonJS build now bundlestemporal-polyfill, while type declarations rely on the standardesnext.temporallib reference. [#823, #925]
@fedify/adonisjs
- Added the new @fedify/adonisjs package, an integration for the AdonisJS framework. It provides a server middleware that mounts a
Federationinside an AdonisJS application, a service provider that owns the federation's lifecycle, anode ace configurehook that scaffolds config/fedify.ts and the federation preload files, and actx.federationrequest context. The package targets Node.js and is published to npm only. [#139, #1006 by Samuel Brinkmann]
@fedify/astro
- Added and continuously tested support for Astro 6 and 7 while retaining Astro 5 compatibility. The Astro example and
fedify inittemplates now use Astro 7 with current Node.js and Deno adapters; Bun uses the tested@astrojs/nodestandalone output instead of the Astro-5-only@nurodev/astro-bunadapter. [#931, #936]
@fedify/cli
-
Added [SvelteKit] option to
fedify initcommand. This option allows users to initialize a new Fedify project with SvelteKit integration. [#892, #971 by Jang Hanarae] -
Added
fedify.com.esas a tunneling service. The CLI pins the service's SSH host key and rejects a mismatched server before exposing a local port. [#940] -
Removed
localhost.runas a tunneling service. The service is no longer available, and the CLI now rejects attempts to use it. [#940] -
Switched the CLI's Temporal runtime dependency from
@js-temporal/polyfilltotemporal-polyfill. [#823, #925] -
Added support for FEP-ef61 portable objects to the
fedifycommand. [#288, #1156, #1199]-
fedify lookupnow looks up portable objects by theirap:andap+ef61:IDs, compatible identifiers, and the WebFinger handles of portable actors, and verifies their Object Integrity Proofs. Previously, it failed to look up portable IDs, and refused portable objects found through compatible identifiers as cross-origin objects. The same applies to the collections that-t/--traversetraverses and the objects that--recursefollows. When no gateway returns an acceptable object, the command tells why, e.g., that the object's proof is invalid. -
Added the
--gatewayoption tofedify lookup,fedify inbox, andfedify webfinger, which specifies the gateways to look up portable objects from, e.g., for portable IDs without@gatewaylocation hints. -
fedify inbox -f/--follownow follows portable actors, and the-a/--accept-followoption offedify inboxand the-a/--accept-followand-r/--reject-followoptions offedify relayaccept portable IDs and compatible identifiers, which match the actor regardless of@gatewaylocation hints and the gateway of a compatible identifier. -
fedify webfingernow accepts portable actor IDs. As such an ID does not tell which server to ask, the command looks up the actor and then its WebFinger address, which consists of itspreferredUsernameand the host of its first gateway, and reports whether the response links back to the actor.
-
-
Fixed
fedify lookup --recursereporting a timeout or another network failure of the first object or of a linked object as a possibly private object, suggesting the-a/--authorized-fetchoption. It now reports the actual cause, e.g., “Request timed out after 10 seconds,” like the other modes offedify lookupdo. [#1156, #1199] -
Fixed the
-p/--allow-private-addressoption offedify webfingerbeing ignored. [#1156, #1199] -
Changed
fedify lookupto time out each request after 10 seconds when the-T/--timeoutoption is not given, since the document loaders it uses now have a default timeout. Previously, there was no timeout by default. The-T/--timeoutoption now also limits how long a request waits for a DNS lookup, though it cannot stop the lookup itself. [#1131, #1169] -
Updated Optique to 1.3.2. This fixes several command-line parsing issues, so options with attached values such as
--timeout=30are handled consistently, typo suggestions for mistyped options are more accurate, and errors for known options are no longer hidden by positional arguments before--. [#1197]
@fedify/debugger
- Fixed the CommonJS debugger build so it no longer requires
@js-temporal/polyfillat runtime. The build now bundlestemporal-polyfill, while type declarations rely on the standardesnext.temporallib reference. [#823, #925]
@fedify/init
- Added a
testtask to projects scaffolded byfedify init. It starts the app, waits for it to become ready, and checks that it resolves a local actor, giving projects a standard smoke test to run right after scaffolding and whenever the app changes afterwards. [#898, #990 by Jang Hanarae] - Added runtime version verification to
fedify init. It checks that the selected Deno, Bun, or Node.js meets Fedify's minimum version, or a higher version required by a framework (such as Astro's Node.js 22.12), before generating a project. A missing, malformed, or unsupported runtime now produces a clear error in non-interactive mode and disables the affected package managers in interactive mode. [#964, #981 by Lee Jeongmin] - Fixed
fedify init's hydration test validation to runformatbeforeformat:check, which previously caused the entire test suite to fail when the package manager isnpmorpnpm: [#950] [#952 by Jang Hanarae] - Supported [SvelteKit] as a web framework option in
fedify init. [#892, #971 by Jang Hanarae]
@fedify/interaction-controls
- Added the new
@fedify/interaction-controlspackage for implementing GoToSocial interaction controls, FEP-044f, and FEP-7aa9. It provides immutable TypeScript APIs for creating and verifying interaction requests and authorizations, evaluatingInteractionPolicy, recognizing bare interactions, and formatting stable storage keys for like, reply, announce, quote, and feature interactions. [#811, #929]
@fedify/lint
-
Added four lint rules for the media upload endpoint introduced in
@fedify/fedify: [#754, #927]media-uploader-object-uri-requiredwarns when asetMediaUploader()callback does not derive its return value fromctx.getObjectUri().media-uploader-authorization-requiredwarns whensetMediaUploader()is registered without an.authorize()hook.actor-upload-media-property-requiredwarns when a media uploader is registered but the actor dispatcher does not advertiseendpoints.uploadMedia.actor-upload-media-property-mismatchwarns whenendpoints.uploadMediais not built withctx.getMediaUploaderUri(identifier).
-
Added the
actor-preferred-username-requiredlint rule, which warns when an actor dispatcher's return value does not include apreferredUsernameproperty. [#895, #1022 by Jae-Hyuk-Jang] -
Added the
outbox-listener-delivery-not-awaitedrule to@fedify/lint. It reports an outbox listener that callsctx.sendActivity()orctx.forwardActivity()and drops the returned promise, so that the activity may never leave on a runtime such as Cloudflare Workers, which discards pending work once the response is returned. A call counts as handled when its promise is awaited, returned, passed toPromise.all()orPromise.allSettled(), or handed towaitUntil(), andvoid,Promise.race()andPromise.any()are accepted as deliberate choices to stop waiting. The ESLintrecommendedconfiguration enables the rule as a warning andstrictas an error, and Oxlint users enable it by name. It is not available in Deno Lint, which turns on every rule of a plugin at once. [#1057, #1067 by Jae-Hyuk-Jang] -
Changed
outbox-listener-delivery-required(@fedify/lint) to decide whether actx.sendActivity()/ctx.forwardActivity()call actually runs, instead of scanning the listener's source as a flat block of text. It now reports a listener whose only delivery calls sit behind a dead branch, after an unconditionalreturn/throw, or inside a function that is never used. When it cannot tell whether a delivery call runs, it stays quiet: a function held under a name counts as used as soon as that name is mentioned, however it is passed around, and an inline callback counts wherever it is passed. [#900, #1050 by Jae-Hyuk-Jang] -
Changed the actor URI mismatch rules to accept the FEP-ef61 portable IDs of actors and collections, and the compatible identifiers built from them, so that applications can use the
getPortable*Uri()methods ofContextin actor dispatchers without disabling these rules. [#288, #1157, #1179] -
Fixed
outbox-listener-delivery-requiredandoutbox-listener-delivery-not-awaited(@fedify/lint) losing track of the functions an object already holds when a nested helper in the listener assigns another property of that object, as intarget.fallback = () => {}. A listener that delivers throughtarget.deliver()after such an assignment is no longer reported as undelivered, and a dropped delivery promise insidetarget.deliver()is now reported. A helper that declares its own object of the same name is still checked separately from the outer one. [#1125, #1140] -
Fixed
outbox-listener-delivery-requiredandoutbox-listener-delivery-not-awaited(@fedify/lint) to properly evaluate reachability in statically false loops (likewhile (false)) and to correctly traversefor...ofandfor...inloop binding patterns. Unreachable loop branches no longer count as deliveries, andoutbox-listener-delivery-not-awaitednow correctly catches dropped promises inside loop binding patterns. Loop default functions are checked only when the bound value or target object is referenced. Delivery helpers used after an assignment-form loop default remain recognized, including when unrelated blocks reuse their names. [#1071, #1088 by @ArchieTansaria]
@fedify/mysql
- Fixed the CommonJS MySQL adapter build so it no longer requires
@js-temporal/polyfillat runtime. The build now bundlestemporal-polyfill, while type declarations rely on the standardesnext.temporallib reference. [#823, #925]
@fedify/netlify
- Added
NetlifyBlobsKvStore, a Netlify Blobs-backed key–value store with expiration, prefix listing, and atomic compare-and-set operations. Netlify deployments can now persist Fedify state and preserve ordered queue delivery without a separate database. [#1010, #1029 by Jiwon Kwon] - Added the new @fedify/netlify package for processing Fedify message queue jobs with Netlify Async Workloads. It provides
NetlifyMessageQueuefor durable event submission andcreateNetlifyQueueHandler()for Netlify Function consumers, including delayed delivery, native retry delegation, non-retryable malformed-event handling, durable per-key FIFO ordering, and explicit recovery for unobservable dead-letter failures. [#930, #934]
@fedify/next
- Added support for FEP-ef61 hashlink media requests, e.g.,
GET /.well-known/apgateway/hl:zQm..., tofedifyWith(). Clients fetch such media with, e.g.,Accept: image/*, so these requests were not passed to Fedify unless they had federation media types in their headers, and Next.js answered them instead of the hashlink media dispatcher.isFederationRequest()now recognizes them by their paths regardless of their headers. To make Next.js run the middleware for them, add{ source: "/.well-known/apgateway/:path*" }to thematcherof your middleware.ts or proxy.ts file. [#288, #1149, #1170] - Added
isHashlinkMediaRequest()function. [#288, #1149, #1170]
@fedify/pglite
- Added the
@fedify/pglitepackage withPgliteKvStore, aKvStorebacked by an embedded PGlite database. It accepts a caller-created PGlite instance and is intended for a single PGlite instance in a single runtime isolate; a message queue is not provided because PGlite does not share data between processes. [#1018, #1020 by ChanHaeng Lee]
@fedify/postgres
- Added
PostgresKvStore.cas(), including atomic creation, replacement, and deletion with TTL-aware comparison. This allows PostgreSQL-backed stores, including Netlify Database, to enforce queue ordering and other Fedify CAS operations. [#930, #934] PostgresKvStorenow creates crash-safe logged tables by default and migrates existing unlogged tables during initialization. Transient unlogged storage remains available with theunloggedoption. The one-time migration rewrites and exclusively locks an existing table, so upgrades with large or busy key–value tables should schedule it accordingly. [#930, #934]- Fixed the CommonJS PostgreSQL adapter build so it no longer requires
@js-temporal/polyfillat runtime. The build now bundlestemporal-polyfill, while type declarations rely on the standardesnext.temporallib reference. [#823, #925]
@fedify/redis
- Added atomic
RedisKvStore.cas()for standalone Redis and Redis Cluster. Redis-backed deployments can now use portable inbox forwarding and other features that need compare-and-swap. Custom codecs must encode equal values identically, and Redis must permitEVAL; deployments that deny scripting can no longer rely on the previous non-CAS fallback. [#1163, #1167] - Fixed the CommonJS Redis adapter build so it no longer requires
@js-temporal/polyfillat runtime. The build now bundlestemporal-polyfill, while type declarations rely on the standardesnext.temporallib reference. [#823, #925]
@fedify/relay
- Fixed the CommonJS relay build so it no longer requires
@js-temporal/polyfillat runtime. The build now bundlestemporal-polyfill, while type declarations rely on the standardesnext.temporallib reference. [#823, #925] - Fixed the relay documentation to use the canonical actor and shared inbox URIs, distinguish Mastodon-style and LitePub-style subscription behavior, and explain which deployment responsibilities remain with applications. [#899, #996 by Jiwon Kwon]
@fedify/sqlite
- Fixed the CommonJS SQLite adapter build so it no longer requires
@js-temporal/polyfillat runtime. The build now bundlestemporal-polyfill, while type declarations rely on the standardesnext.temporallib reference. [#823, #925]
@fedify/testing
-
Added support for FEP-ef61 portable objects to the mock federation and contexts, following the new APIs of
@fedify/fedify, so that tests can exercise portable objects without a live gateway. [#288]-
Added the
getPortableActorUri(),getPortableObjectUri(),getPortableInboxUri(),getPortableOutboxUri(),getPortableFollowingUri(),getPortableFollowersUri(),getPortableLikedUri(),getPortableFeaturedUri(),getPortableFeaturedTagsUri(), andgetPortableCollectionUri()methods to the mock contexts. They rejectdid:keyDIDs that are not encoded in base58-btc. [#835, #839, #841, #1092, #1111, #1114, #1142] -
Added the
portableoption toparseUri()of the mock contexts. Without it,parseUri()no longer recognizes a portable ID or compatible identifier whose path starts with/users/; with it, the result has the DID in itsauthorityproperty. It also returnsnullfornull. [#1143, #1145] -
The mock contexts that
createContext()creates keep theverifyPortableObjectproperty given to them, and theirlookupObject()andtraverseCollection()pass it on.createFederation()accepts a fixturedocumentLoader, acontextLoader, andverifyPortableObject, which mock context lookups use for portable IDs. [#1107, #1120, #1161, #1196] -
Added the
isSignedByAudience()method to the mock request contexts, which checks the audience against the actor thatgetSignedKeyOwner()returns. [#1153, #1183] -
federation.createContext()accepts an explicitportableRequestfor a request context, which dispatchers can inspect. [#1161, #1196] -
Added the
mapPortableActorId()method to the setters thatMockFederation.setActorDispatcher()returns, themapPortableOwner()method to the mock custom collection setters, and thesetHashlinkMediaDispatcher()method to the mock federation, which serves hashlink media responses. [#838, #840, #1080, #1099, #1111, #1142, #1161, #1196]
-
-
Added support for registering
onRequestFinished()on mock federations so applications can reuse their inbox configuration in tests. [#1191, #1201] -
Added
testKvStore(), a conformance test suite forKvStoreimplementations, complementingtestMessageQueue(). [#1018, #1020 by ChanHaeng Lee] -
Changed
getObject()of the mock context thatcreateFederation()creates to followRequestContext.getObject()of@fedify/fedify: it now returnsnullfor aTombstonethat the object dispatcher returns, unless the tombstone is an instance of the requested class or{ tombstone: "passthrough" }is given. [#1112, #1117] -
Fixed the CommonJS testing utilities build so it no longer requires
@js-temporal/polyfillat runtime. The build now bundlestemporal-polyfill, while type declarations rely on the standardesnext.temporallib reference. [#823, #925]
@fedify/vocab
-
Added support for FEP-ef61 portable objects, whose IDs are
ap:orap+ef61:URIs with a DID instead of a host, e.g.,ap://did:key:z6Mk.../actor, and which are retrieved through the servers listed in their actors'gateways. See the Portable objects chapter of the manual for the FEP-ef61 profile that Fedify supports. [#288]-
Generated vocabulary classes now accept portable IDs with decoded or percent-encoded DID authorities wherever an IRI is expected, and serialize them as canonical
ap+ef61:IRIs with decoded DID authorities. Inspecting vocabulary objects, e.g., withconsole.log(), also shows them in this form. [#826, #850, #1156, #1199] -
Added the
gatewaysproperty to actor classes, and thedigestMultibaseproperty toLinkand document and media classes. Gateways are serialized as origins without a trailing slash. A portable actor that uses thegatewaysterm without mapping it in its JSON-LD context, as tootik does, is also read. [#830, #928, #1097, #1202] -
Added the optional
Recipient.gatewaysproperty, through which Fedify delivers activities to portable inboxes. Applications that buildRecipientobjects by hand, e.g., in followers collection dispatchers, need to set it for portable actors. [#1147, #1180] -
Property accessors such as
Create.getObject()now fetch portable references through gateways: those in the newgatewaysoption, or else those in the@gatewaylocation hints of the reference. A reference without hints that has the same DID as a portable actor it was reached from, e.g., the actor'soutbox, is fetched through that actor'sgateways. A fetched object is returned only if its@ididentifies the referenced portable object and the verifier given as the newverifyPortableObjectoption accepts it, typicallyverifyPortableObject()from@fedify/fedify, or aContextpassed as the options. Portable references cannot be dereferenced without the option, andcrossOrigin: "trust"does not skip these checks. [#834, #1077, #1093, #1105] -
Property accessors tell the
verifyPortableObjectfunction where a portable object was retrieved from and which objects led to it, so that it can accept a portable collection without a proof if a gateway that its owner lists served it. Objects embedded in such a collection are fetched and verified one by one. Added thecrossOrigin,gateways, andverifyPortableObjectoptions toTraverseCollectionOptionsas well. [#836, #1084] -
Added the
verifyPortableObjectoption to the constructors, thefromJsonLd()methods, and theclone()methods of vocabulary classes, which sets the verifier that the object's property accessors use by default. Objects embedded in the parsed document, and objects that accessors andtraverseCollection()fetch, get the verifier of the call or of their parent by default, so that, e.g.,(await create.getObject(ctx))?.getAttribution()verifies portable objects without passingctxagain. Having a default verifier does not mean that an object was verified. Added theinheritPortableObjectVerifieroption to property accessors andTraverseCollectionOptionsto limit a verifier to a single call. [#1107, #1120, #1129, #1137] -
Added the
verifyPortableObjectandgatewaysoptions toLookupObjectOptions. With the former,lookupObject()looks up portable IDs through their@gatewaylocation hints or the gateways given by the latter, compatible identifiers through the gateways they name, and the handles of portable actors through their WebFinger responses, trying at most five gateways per lookup. The gateways that it infers from compatible identifiers, WebFinger responses, and location hints are passed to theverifyPortableObjectfunction asgatewayHints, whilegatewayshas the ones given explicitly. [#837, #1082, #1090, #1091, #1158, #1194] -
getActorHandle()now supports portable actors. It takes the domain of a portable actor's handle from the first gateway in itsgateways, and returns the handle only if its WebFinger response links back to the actor. [#837, #1082] -
Exported the portable object verifier types and other types used in vocabulary API signatures from
@fedify/vocab, so that custom verifiers can be typed without importing@fedify/vocab-runtime. [#1160, #1184]
-
-
Changed property accessors and
lookupObject()so that they no longer trust FEP-ef61 compatible identifiers, i.e., HTTP(S) URLs under a gateway's /.well-known/apgateway/ path such ashttps://gw.example/.well-known/apgateway/did:key:z6Mk.../actor, because of the origin that serves them. Anyone can serve a compatible identifier for any DID, so previously anyone could serve an unsigned object that claimed to be someone else's portable object. [#288, #837, #1082, #1090, #1091, #1107, #1120]-
With the
verifyPortableObjectoption, a compatible identifier is dereferenced as the portable object it stands for: through the gateway that it names and then the gateways in thegatewaysoption, only if the option accepts the object. A document fetched from an ordinary HTTP(S) URL is verified the same way if its final URL is a compatible identifier or its@idis a portable ID. Malformed compatible identifiers are rejected without a request. -
Without the option, accessors keep fetching compatible identifiers as ordinary HTTP(S) URLs, but no longer cache the results in the parent objects, so that a later call with the option verifies them. However, accessors of an object whose ID is a portable ID or a compatible identifier now throw a
TypeError(or returnnullwithsuppressError: true) for such references without the option, as for portable IDs. -
Accessors no longer trust an embedded object whose
@idis a compatible identifier, or a portable ID with a DID other than its parent's, even withcrossOrigin: "trust"; they dereference and verify it on its own. Likewise,crossOrigin: "trust"no longer makeslookupObject()return an object with a portable@idfrom a document URL of another origin.
-
-
Updated FEP-fe34 cross-origin checks to understand the cryptographic origins of FEP-ef61 portable IDs and DID URLs. Property accessors and
lookupObject()now treatap:andap+ef61:IDs anddid:keyverification method IDs as the same origin when their DIDs match. [#288, #829, #926] -
Changed nested serialization so that an object carrying the signed JSON-LD representation that
signObject()retains is embedded with that exact representation, including its own@context, rather than being rebuilt under the parent's context, so that its proof keeps verifying.clone()never carries the retained representation, because a clone may differ from the document the proof covers. [#288, #1044, #1051] -
Added vocabulary support for FEP-7aa9, including
FeaturedCollection,FeaturedItem,FeatureRequest, andFeatureAuthorization, plus actorfeaturedCollectionsandInteractionPolicy.canFeatureproperties. [#810, #914] -
Added the
Endpoints.uploadMediaproperty, the standard ActivityStreams endpoint for the ActivityPub Media Upload extension. [#754, #927] -
Fixed the CommonJS vocabulary build so it no longer requires
@js-temporal/polyfillat runtime. The build now bundlestemporal-polyfill, while type declarations rely on the standardesnext.temporallib reference. [#823, #925] -
Added vocabulary support for the FEP-22cd draft, associating each translated version with its translators, source object, and optional source review timestamp. [#1037, #1038]
- Added
Translationclass withid,language,original,sourceUpdated,basis,url, andurlsproperties. - Added
Translation.getTranslator()/Translation.translatorIdandTranslation.getTranslators()/Translation.translatorIdsfor accessing credited actors. The constructor acceptstranslatorandtranslatorsvalues. - Added
Object.translationsproperty, inherited byArticle,Note, and other object types, for per-language translation metadata without creating separate posts.
- Added
@fedify/vocab-runtime
-
Added https://w3id.org/fep/22cd to preloaded JSON-LD contexts. [#1037, #1038]
-
Added support for FEP-ef61 portable objects, whose IDs are
ap:orap+ef61:URIs with a DID instead of a host, e.g.,ap://did:key:z6Mk.../actor. See the Portable objects chapter of the manual for the FEP-ef61 profile that Fedify supports. [#288]-
Added
parseIri(),formatIri(),parseJsonLdId(), andhaveSameIriOrigin(), which parse, format, and compare IRIs, including portable IDs with decoded or percent-encoded DID authorities. TheURLobjects that represent portable IDs keep their DIDs percent-encoded, andformatIri()formats them in their canonical form, e.g.,ap+ef61://did:key:z6Mk.../actor. Portable IDs and compatible identifiers whose paths have.or..segments, which theURLclass would remove, are rejected with aTypeError. [#826, #850, #1154, #1186] -
Added
canonicalizePortableUri()andarePortableUrisEqual()for comparing portable IDs. They accept both schemes with decoded or percent-encoded DID authorities, normalize them toap+ef61:, and ignore the query, including@gatewaylocation hints. Fedify deliberately canonicalizes toap+ef61:rather thanap:, which FEP-ef61 currently recommends, and this may change in Fedify 3.0, so compare portable IDs witharePortableUrisEqual()rather than as strings. [#828, #924, #1151, #1198] -
Added
getFe34Origin()andhaveSameFe34Origin(), which compare FEP-fe34 origins: HTTP(S) URLs have their web origins, while portable IDs and DID URLs have their DIDs as their cryptographic origins. [#829, #926] -
Added
exportDidKey(),importDidKey(), andparseDidKeyVerificationMethod()for Ed25519did:keyDIDs and their verification method DID URLs.exportDidKey()always encodes DIDs in base58-btc, as FEP-ef61 requires. [#827, #915] -
Added
toCompatibleEf61Id()andfromCompatibleEf61Id()for converting between portable IDs and compatible identifiers, i.e., HTTP(S) URLs under a gateway's /.well-known/apgateway/ path such ashttps://example.com/.well-known/apgateway/did:key:z6Mk.../actor, which software without portable ID support can use.fromCompatibleEf61Id()returnsnullfor URLs that are not compatible identifiers, and throws aTypeErrorfor malformed ones, including those with location hints. Converting a compatible identifier does not authenticate it. [#833, #1074] -
Added
isGatewayUrl()andparseGatewayUrl(), which check that a gateway is an HTTP(S) origin without credentials, a path, a query, or a fragment, as FEP-ef61 requires. [#830, #928, #1176, #1190] -
Added
withGatewayHints(),withoutGatewayHints(), andgetGatewayHints()to add, remove, and read the@gatewaylocation hints of portable IDs, which tell consumers where to retrieve a referenced portable actor. [#1159, #1189] -
Added SHA-256
digestMultibaseandhl:hashlink helpers:computeDigestMultibase(),parseDigestMultibase(),verifyDigestMultibase(),createHashlink(),parseHashlink(), andverifyHashlink(). [#831, #935] -
Added
fetchPortableMedia(), which retrieves the media of a portable object through its owner's gateways, or an HTTP(S) resource directly, and returns it only after verifying itsdigestMultibase. It limits the response size and rejects private network addresses by default. [#1152, #1182] -
Added the FEP-ef61 JSON-LD context to the preloaded contexts, so that
gatewaysanddigestMultibasecan be compacted and expanded without fetching the context. [#830, #928] -
Added the
PortableObjectVerifier,PortableObjectVerifierOptions,PortableObjectVerification, andPortableObjectReferrertypes, which describe theverifyPortableObjectoption of property accessors. A verifier receives a fetched document along with its final URL, the gateways used, and the chain of objects that referred to it, and can accept a collection without a proof asunsecured. [#834, #836, #1077, #1084] -
Added the
verifyPortableObjectproperty toPropertyPreprocessorContext, the default verifier that objects returned by a property preprocessor should use. [#1107, #1120]
-
-
Changed the
Acceptheader that document loaders send when fetching ActivityPub objects toapplication/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams", as ActivityPub and FEP-ef61 gateways require. Previously, the JSON-LD media type lacked the ActivityStreams profile. [#288, #834, #1077] -
Added the FEP-7aa9 JSON-LD context to the preloaded context registry so FEP-7aa9 documents can be compacted and expanded without fetching the context remotely. [#810, #914]
-
Changed
getDocumentLoader()to reject HTML and XHTML responses that do not advertise an ActivityPub alternate document with aFetchErrorinstead of attempting to parse the HTML as JSON. This makes remote HTML error pages surface as document loading failures with the response URL and content type, rather than generic JSON parser crashes. [#912, #913] -
Changed
getDocumentLoader()to time out each call after 10 seconds by default. The time limit covers the whole call, including every redirect and alternate document it follows and reading the response body. A call that times out throws aFetchErrorwithout a response, whosecauseis aDOMExceptionnamed"TimeoutError". AnAbortSignalpassed as thesignaloption still cancels a call as before. [#1131, #1169]- Added
DocumentLoaderFactoryOptions.timeoutoption, in milliseconds, to change the time limit, or to turn it off withnull.
- Added
-
Changed
getDocumentLoader()to read the body of an error response before it throws aFetchError, at most 1 MiB, so that the time limit also bounds reading it.FetchError.responsekeeps the body byte for byte, or only the status and headers if the body is larger than that. [#1131, #1169]
@fedify/vocab-tools
- Added the
extraContextproperty schema option to include a JSON-LD context only when its terms are used, preserving existing output for objects without the extension. [#1037, #1038] - Added the
trustEmbeddedObjectstype schema option so embedded metadata identifiers need not establish trust in linked actors. [#1037, #1038] - Changed suppressed vocabulary fetch and parsing failures to log at the warning level so that intentionally handled failures are not reported as application errors. [#933, #1035 by Jae Hui Hong]