Repository navigation
Allow session.publish()/AuthorizedMessage.update() to set name, summary, and url #40
Description
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 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 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.- Fedify's
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 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
Createfollowed immediately by anUpdate, 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-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, 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
SessionPublishOptionsis 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
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:
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
Objectsupports 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
Objectconstructor 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.
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Effort
Activity
[-]Allow session.publish() / AuthorizedMessage.update() to set name, summary, and url[/-][+]Allow `session.publish()`/`AuthorizedMessage.update()` to set name, summary, and url[/+]dahlia commentedon Aug 23, 2026
Thanks for the detailed report. I agree that this is a gap in BotKit's public API. I'll implement option 1. BotKit will expose these three ActivityStreams properties directly rather than accept arbitrary constructor options, since BotKit still needs to control object identity, attribution, and addressing.
The API will have this shape:
Leaving
urlunset when publishing will retain the current default, which points to BotKit's message page. For updates, an omitted field will preserve the stored value,nullwill remove it, and any other value will replace it. Fedify'sclone()does not clear these singular properties when givennull, so the implementation will translate removal to emptynames,summaries, orurlsarrays.Activity Vocabulary defines
summaryas an HTML fragment, and its examples use paragraphless inline markup. I'll also add aninlinetagged template that returnsText<"inline", TContextData>, making summaries composable without treating strings as trusted HTML:Literal text and string summaries will be escaped. The inline template will render line breaks as
<br>and reject interpolated blockTextvalues. Tags produced by a summary, including mentions and custom emoji, will be collected along with the content tags. Mentions will participate in addressing in the same way as mentions incontent.When
languageis supplied at publication time, it will apply tonameandsummaryas well ascontent, allowing the corresponding language maps to be emitted.One correction to the Mastodon description: current Mastodon converts
Articleand the other converted object types fromname,summary, andurl, falling back toidfor the link. It does not usecontentin that path. This does not change the requested API, but it makesnameandsummarymore important for this use case.Add message metadata support
dahlia commentedon Aug 24, 2026
Implemented in #42 and will be shipped with the next minor release, BotKit 0.6.0. Meanwhile, you can try v0.6.0-dev.345+20d0a60b, a pre-release build.
moreal commentedon Aug 24, 2026
@dahlia Thank you!