Written with Claude Code
Background
ActivityStreams 2.0 objects (which Note/Article/etc. extend) have several standard properties that BotKit currently never lets a bot set:
name — a title.
summary — a short teaser, distinct from content.
url — a human-facing permalink for the object, distinct from id. Per the spec, id must be a dereferenceable AS2 document on the publishing server, while url may point anywhere (e.g. an external site the object represents).
Mastodon treats Note as a first-class type (renders content inline) but converts everything else, including Article, "as best as possible": it uses content (or name if content is absent) as the status text and appends url to it. url is also documented as the value Mastodon uses for a status's permalink. So for any non-Note object — and for url on Note too — these three fields are the only way to control how Mastodon (and similarly-behaved implementations) render and link a bridged post.
Looking at the current implementation:
SessionImpl.publish() (session-impl.ts) builds the message with new cls({ ..., url: this.bot.instance.getMessageWebUrl(...) }). url is always the bot's own message page, with no way to override it. name and summary aren't passed to the constructor at all, regardless of cls.
SessionPublishOptions (session.ts) only exposes language, visibility, attachments, quoteTarget, and quotePolicy.
AuthorizedMessageImpl.update() (message-impl.ts) — the officially supported way to edit an already-published message — clones the stored object with contents, tags, tos, ccs, updated, and interactionPolicy. name, summary, and url are not part of the patch here either.
AuthorizedMessageUpdateOptions only exposes quotePolicy.
- Fedify's
Object base class (which Note/Article extend) already supports name, summary, and url in both its constructor and clone() — this is purely a gap in what BotKit exposes, not a Fedify limitation.
Motivation
Any bot that mirrors or bridges existing content — RSS/Atom bridges, blog cross-posters, link-sharing bots — needs to attach a title, a teaser, and a link back to the source when it isn't the canonical content. Right now that's impossible through the public API, and this isn't a hypothetical bot category: BotKit's own examples/rss-bot tutorial hits exactly this. Its pollFeed() never uses Article at all and publishes a plain Note with the title and link crammed into content as text:
await session.publish(
text`${item.title ?? "(untitled)"}
${link(item.url ?? feed.url)}`,
);
That's a reasonable fallback given what's available today, but it means the item's own link never becomes the post's url (the permalink stays the bot's own message page), and there's no way for this tutorial to grow into publishing Articles with a real title/teaser instead of a two-line Note. The same shape shows up in RSS-to-Mastodon bridges outside the BotKit ecosystem too — e.g. RSS Parrot, Mastofeeder — which is the kind of bot this issue is about, independent of BotKit specifically.
As a workaround in our own bot, we (an RSS→ActivityPub bridge) publish the message, then immediately reach into Repository.updateMessage() ourselves to clone the stored Create's object with name/summary/url set, and hand-roll an Update activity to broadcast it to followers. This duplicates most of what AuthorizedMessage.update() already does internally — we only bypass it because it doesn't expose the fields we need.
Two concrete costs of the current gap:
- Extra round trip. Every post needs a
Create followed immediately by an Update, doubling outgoing federation traffic, purely because the fields can't be set at publish time.
- Reimplemented internals. Bots are forced to depend on
Repository.updateMessage() and hand-build Update activities instead of using the public AuthorizedMessage.update() API, because that API's option surface doesn't cover this case.
Related Issues / PRs
None found — searched name, summary, title, url, Article and publish across open and closed issues.
Proposed Solutions
💡 If you have a better approach, please comment. Below are the options currently under consideration.
Option 1: Add name/summary/url as explicit fields
Add three new optional fields, mirroring the AS2 property names directly:
interface SessionPublishOptions<TContextData> {
// ...existing fields...
readonly name?: string;
readonly summary?: Text<"inline", TContextData> | string;
readonly url?: URL; // defaults to the current behavior (bot's own message page) when omitted
}
interface AuthorizedMessageUpdateOptions {
readonly quotePolicy?: QuotePolicyOption;
readonly name?: string;
readonly summary?: Text<"inline", TContextData> | string;
readonly url?: URL;
}
session-impl.ts's publish() and message-impl.ts's update() would pass these straight into the new cls({...}) / object.clone({...}) calls they already make, the same way attachments or interactionPolicy are handled today.
await session.publish(text, {
class: Article,
name: "Original post title",
summary: teaserHtml,
url: new URL("https://original-blog.example/post-slug"),
});
Pros:
- Discoverable and type-checked; matches how the rest of
SessionPublishOptions is shaped.
- No new concepts to document — each field maps 1:1 to a well-known AS2 property.
- Small, additive, backward-compatible diff confined to two functions.
Cons:
- Every future AS2/Fedify
Object field a bot might want (e.g. attributedTo overrides, sensitive, tag beyond mentions) would need its own dedicated addition later.
Option 2: A generic object-field passthrough
Instead of naming fields one at a time, accept a partial patch of the underlying Fedify object's constructor options:
interface SessionPublishOptions<TContextData> {
// ...existing fields...
readonly objectOptions?: Partial<ConstructorParameters<typeof Note>[0]>;
}
which is merged into the object BotKit already builds.
Pros:
- Future-proof: any AS2 field Fedify's
Object supports becomes available without further BotKit API changes.
- One escape hatch instead of a growing list of named options.
Cons:
- Weaker type safety/discoverability — callers need to know Fedify's
Object constructor shape, not just BotKit's docs.
- Exposes internal object construction as public surface, which could make it easier to accidentally clobber fields BotKit itself relies on (
id, attribution, tos/ccs, etc.) unless carefully guarded/filtered.
- Couples the option's exact type to whichever Fedify version BotKit currently depends on.
Already Answered
Q: Why not just put the title/link in content instead?
For Note, that's already what bots typically do (see the examples/rss-bot snippet above), and it works well enough for the body text. The gap is specifically Article and other non-Note types, which Mastodon renders from content/name plus a separately-appended url — and separately, url also drives the status permalink for any object type. Putting the link only inside content still leaves the permalink pointing at the bot's own message page instead of the source, regardless of what either option above ends up looking like.
Q: Should url always point back to the bot's own page by default?
Worth discussing — this issue isn't proposing a specific default, just a way to override url at all. Per the AS2 spec, url is explicitly allowed to point elsewhere, which is how bridges, cross-posters, and read-more links are meant to work, so whatever the default ends up being, it should be overridable.
Written with Claude Code
Background
ActivityStreams 2.0 objects (which
Note/Article/etc. extend) have several standard properties that BotKit currently never lets a bot set:name— a title.summary— a short teaser, distinct fromcontent.url— a human-facing permalink for the object, distinct fromid. Per the spec,idmust be a dereferenceable AS2 document on the publishing server, whileurlmay point anywhere (e.g. an external site the object represents).Mastodon treats
Noteas a first-class type (renderscontentinline) but converts everything else, includingArticle, "as best as possible": it usescontent(ornameifcontentis absent) as the status text and appendsurlto it.urlis also documented as the value Mastodon uses for a status's permalink. So for any non-Noteobject — and forurlonNotetoo — these three fields are the only way to control how Mastodon (and similarly-behaved implementations) render and link a bridged post.Looking at the current implementation:
SessionImpl.publish()(session-impl.ts) builds the message withnew cls({ ..., url: this.bot.instance.getMessageWebUrl(...) }).urlis always the bot's own message page, with no way to override it.nameandsummaryaren't passed to the constructor at all, regardless ofcls.SessionPublishOptions(session.ts) only exposeslanguage,visibility,attachments,quoteTarget, andquotePolicy.AuthorizedMessageImpl.update()(message-impl.ts) — the officially supported way to edit an already-published message — clones the stored object withcontents,tags,tos,ccs,updated, andinteractionPolicy.name,summary, andurlare not part of the patch here either.AuthorizedMessageUpdateOptionsonly exposesquotePolicy.Objectbase class (whichNote/Articleextend) already supportsname,summary, andurlin both its constructor andclone()— this is purely a gap in what BotKit exposes, not a Fedify limitation.Motivation
Any bot that mirrors or bridges existing content — RSS/Atom bridges, blog cross-posters, link-sharing bots — needs to attach a title, a teaser, and a link back to the source when it isn't the canonical
content. Right now that's impossible through the public API, and this isn't a hypothetical bot category: BotKit's ownexamples/rss-bottutorial hits exactly this. ItspollFeed()never usesArticleat all and publishes a plainNotewith the title and link crammed intocontentas text:That's a reasonable fallback given what's available today, but it means the item's own link never becomes the post's
url(the permalink stays the bot's own message page), and there's no way for this tutorial to grow into publishingArticles with a real title/teaser instead of a two-lineNote. The same shape shows up in RSS-to-Mastodon bridges outside the BotKit ecosystem too — e.g. RSS Parrot, Mastofeeder — which is the kind of bot this issue is about, independent of BotKit specifically.As a workaround in our own bot, we (an RSS→ActivityPub bridge) publish the message, then immediately reach into
Repository.updateMessage()ourselves to clone the storedCreate's object withname/summary/urlset, and hand-roll anUpdateactivity to broadcast it to followers. This duplicates most of whatAuthorizedMessage.update()already does internally — we only bypass it because it doesn't expose the fields we need.Two concrete costs of the current gap:
Createfollowed immediately by anUpdate, doubling outgoing federation traffic, purely because the fields can't be set at publish time.Repository.updateMessage()and hand-buildUpdateactivities instead of using the publicAuthorizedMessage.update()API, because that API's option surface doesn't cover this case.Related Issues / PRs
None found — searched
name,summary,title,url,Articleandpublishacross open and closed issues.Proposed Solutions
Option 1: Add
name/summary/urlas explicit fieldsAdd three new optional fields, mirroring the AS2 property names directly:
session-impl.ts'spublish()andmessage-impl.ts'supdate()would pass these straight into thenew cls({...})/object.clone({...})calls they already make, the same wayattachmentsorinteractionPolicyare handled today.Pros:
SessionPublishOptionsis shaped.Cons:
Objectfield a bot might want (e.g.attributedTooverrides,sensitive,tagbeyond mentions) would need its own dedicated addition later.Option 2: A generic object-field passthrough
Instead of naming fields one at a time, accept a partial patch of the underlying Fedify object's constructor options:
which is merged into the object BotKit already builds.
Pros:
Objectsupports becomes available without further BotKit API changes.Cons:
Objectconstructor shape, not just BotKit's docs.id,attribution,tos/ccs, etc.) unless carefully guarded/filtered.Already Answered
Q: Why not just put the title/link in
contentinstead?For
Note, that's already what bots typically do (see theexamples/rss-botsnippet above), and it works well enough for the body text. The gap is specificallyArticleand other non-Notetypes, which Mastodon renders fromcontent/nameplus a separately-appendedurl— and separately,urlalso drives the status permalink for any object type. Putting the link only insidecontentstill leaves the permalink pointing at the bot's own message page instead of the source, regardless of what either option above ends up looking like.Q: Should
urlalways point back to the bot's own page by default?Worth discussing — this issue isn't proposing a specific default, just a way to override
urlat all. Per the AS2 spec,urlis explicitly allowed to point elsewhere, which is how bridges, cross-posters, and read-more links are meant to work, so whatever the default ends up being, it should be overridable.