Skip to content

feat: add getSyndicationUrl hook to syndicator interface - #882

Draft
aciccarello wants to merge 4 commits into
getindiekit:mainfrom
aciccarello:feat/syndication-preprocess-hook-endpoints
Draft

aciccarello wants to merge 4 commits into
getindiekit:mainfrom
aciccarello:feat/syndication-preprocess-hook-endpoints

Conversation

@aciccarello

Copy link
Copy Markdown
Collaborator

Closes #860

Summary

Adds an optional getSyndicationUrl(publication) hook to the syndicator plugin interface, enabling syndication URLs to be written into post files at creation time — without requiring a database or async syndication step.

This is needed for webmention-based syndication targets where the syndication URL is known at post creation time, but mp-syndicate-to is stripped before the file is written (correct per the Micropub spec — it is a server command, not a content property).

Changes

endpoint-syndicate

  • syndicate() is now optional on syndicator targets. Targets without it are skipped rather than throwing. This allows syndicators that only implement getSyndicationUrl() to register without providing an empty no-op.

endpoint-micropub

  • In post-content.js create(), after resolving syndication targets and before getPostTemplateProperties() strips mp-* keys, calls getSyndicationUrl(publication) on any target whose info.uid is in mp-syndicate-to.
  • Returned URLs are normalized to an array, deduplicated against existing syndication values (exact string match), and appended to properties.syndication.
  • Errors from getSyndicationUrl() are caught and logged via debug() — the create request is never failed.

Interface

// Optional method to add to a syndicator target
async getSyndicationUrl(publication) {
  // return string | string[] | null | undefined
}

A syndicator implementing this hook can return the syndication URL synchronously at post creation time. The endpoint handles appending it to syndication before the file is written.

This is scoped to create() only. Update behaviour is a separate concern.

@aciccarello
aciccarello marked this pull request as ready for review August 17, 2026 03:48
@aciccarello
aciccarello marked this pull request as draft August 17, 2026 18:10
@paulrobertlloyd paulrobertlloyd changed the title feat: add getSyndicationUrl hook to syndicator interface feat: add getSyndicationUrl hook to syndicator interface Aug 23, 2026
@paulrobertlloyd
paulrobertlloyd force-pushed the main branch 2 times, most recently from 67b3847 to fa1d368 Compare August 27, 2026 20:07
@rmdes

rmdes commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

The hook solves the ordering problem in the right place — running it in create() before the template renders is what makes the URL available to the file, which is the part that can't be fixed afterwards.

One thing worth surfacing, because this PR works around it without naming it. getSyndicationTarget matches by URL origin:

const targetOrigin = new URL(target.info.uid).origin;
const syndicateToOrigin = new URL(syndicateTo).origin;
return targetOrigin === syndicateToOrigin;

IndieNews registers one target per language on a single origin, so those collapse into one. With en and fr targets registered:

mp-syndicate-to  https://news.indieweb.org/fr   ->  IndieNews (en)

This PR matches info.uid exactly instead, which gets it right — but the codebase now has two matching rules for the same concept. Worth deciding which is intended, since the origin-based one is wrong for any syndicator registering several targets on one host. My own IndieNews syndicator has the same shape, so it hits this today.

Smaller point: getSyndicationUrl failures are caught and sent to debug(). For a hook returning a constant that's fine, but as a general interface a failure means the URL silently isn't written, with nothing to indicate why.

rmdes added a commit to rmdes/indiekit-syndicator-indienews that referenced this pull request Aug 27, 2026
Indiekit selects a syndication target by comparing URL origins, and every
IndieNews language shares one. A request for `https://news.indieweb.org/fr`
is therefore handed to whichever IndieNews target registered first, which is
usually the English one.

That is not only a wrong label. The webmention names the channel it is
submitted to, and IndieNews requires the post to link to that same channel,
so a post carrying a `u-syndication` link to `/fr` submitted with a target of
`/en` is rejected.

Read the language back out of `mp-syndicate-to`, which Indiekit passes to
`syndicate()`, and fall back to the configured one when no IndieNews target
was requested.

This covers one language per post. Requesting two in a single post still
loses the second: `hasSyndicationUrl` also compares origins, so once one
IndieNews permalink is recorded the rest are treated as already syndicated.
That needs the exact matching proposed upstream in getindiekit/indiekit#882.

Adds the first tests for this package, run with `node --test`.
@aciccarello

Copy link
Copy Markdown
Collaborator Author

That's a good point about getSyndicationTarget. I would like to make that check logic shared.

But the same-origin issue needs to be addressed better. I was thinking syndication targets should be able to match themselves using their own logic with the origin check as a fallback. This would probably look like another method hook on the targets. So the indienews targets would match the whole URL, while other syndicators can match more loosely.

But I'm not sure that's really needed. The alternative would be to move all syndicators to match the URL completely. Since the syndicators are setting the UID themselves, I believe that should be fine, no?

The difficult logic is the "hasSyndicationUrl" which checks the processed syndication URL for pre-existing values. The url for the post isn't going to match the UID for the syndicator, at least not exactly. To address the IndieNews case, a more general string includes match over checking the origin might be suffice. But I'm not 100% sure about that. Still, it seems better than needlessly complicating the syndication target API.

In summary I'm proposing:

  • getSyndicationTarget
    • moved to a shared location for both the syndicator and micropub endpoint plugins
    • Check that the URL contains the syndicator UID instead of an origin check
  • hasSyndicationUrl
    • moved to a shared location for both the syndicator and micropub endpoint plugins
    • Check that the URL contains the syndicator UID instead of an origin check

@rmdes

rmdes commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Both moves make sense, and sharing the two helpers between endpoint-micropub and endpoint-syndicate is overdue — they've drifted.

One caution on swapping the origin check for "URL contains the UID": it doesn't fix the IndieNews case I described on #880. The pre-submission link is exactly https://news.indieweb.org/en, which contains the UID exactly, so a contains-match still reports it as already syndicated. It does make the positive case correct — a real permalink under /en/ genuinely means it was syndicated — so it's still an improvement, just not a fix for that particular false positive.

What would fix it is distinguishing "a syndication value pointing at this target" from "a syndication value this target produced". getSyndicationUrl from this PR is arguably that seam already.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make syndication available to file at creation for webmention based syndication

2 participants