Implementing the Organization (Service) + Group + Person structure in WordPress ActivityPub #2322
Jiwoon-Kim
started this conversation in
Ideas
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
“Do not summarize or paraphrase. Translate literally, preserving line breaks, punctuation, and markdown structure exactly as in the source.”
You are a professional multilingual translator.
Translate the following text into [English] without omitting, merging, or reordering any part of the original structure.
Keep:
Do not summarize or interpret the meaning — translate literally but naturally.
Preserve the full structure of the source text.
[INPUT TEXT BELOW]
Implementing the Organization (Service) + Group + Person structure in WordPress ActivityPub
Study on use cases by actor type
https://ko.wordpress.org/plugins/user-activity-log/
Person actor implementation examples
WP User Frontend Pro, Ultimate Member, Profile Builder
→ These plugins allow customization of user profile pages.
https://wordpress.org/plugins/ultimate-member/
https://ultimatemember.com/extensions/profile-tabs/
https://ultimatemember.com/extensions/user-notes/
https://ultimatemember.com/extensions/user-locations/
https://ultimatemember.com/extensions/friends/
https://ultimatemember.com/extensions/followers/
https://ultimatemember.com/extensions/verified-users/
https://ultimatemember.com/extensions/real-time-notifications/
https://ultimatemember.com/extensions/social-activity/
https://ultimatemember.com/extensions/user-tags/
https://ultimatemember.com/extensions/groups/
https://ultimatemember.com/extensions/user-photos/
In the basic structure of WordPress, it is designed as “one account = one user profile”, so it does not natively support having multiple profiles (or pages/characters/brands) under one account like Facebook.
However, it can be implemented through several approaches and plugin combinations.
① When you want to switch between multiple profiles (alias accounts, brands, characters)
Method: Create and switch between multiple “sub-profiles” under one main account
This is not possible with the default WordPress functionality, but it can be done using user role extension plugins + custom profile features.
Use
user_metato store multiple “profile slots.”Example:
Implement a “profile switch” button in the frontend UI → select the current profile using a session variable such as
set_current_profile()When writing posts, overwrite
post_author_metaor the ActivityPub actor URL based on the “current profile.”➡ As a result, you can stay logged in with one account and use the “profile switch” menu to act under multiple personas.
(Very similar to Facebook’s “page switch”)
② When you want to operate a “separate feed for each profile” within the blog
Method: Custom Post Type + Author Meta + ActivityPub integration
but by slightly extending
ActivityPub\Actor_Factory, you can also create profile-level actors.Example flow
Main account:
@[email protected]Sub accounts (profiles):
@[email protected](pet chicken daily feed)@[email protected](design·AI feed)➡ Separate actor endpoints in the plugin (
/actor/{profile}form)➡ Each profile has its own public key, outbox/inbox URL
➡ The admin just logs into one WordPress account and switches profiles.
This requires customization of ActivityPub, but is fully implementable.
Implementing a “Group actor that has multiple moderators/admins in a shared profile form in WordPress corresponds to an advanced design that intersects the existing ActivityPub spec and WordPress’s user structure.
🧠 First, conceptual clarification
In ActivityPub, the
Grouptype actor exists and is structurally defined as follows:{ "@context": "https://www.w3.org/ns/activitystreams", "type": "Group", "id": "https://example.com/@busantravel", "name": "Travel in Busan", "attributedTo": [ "https://example.com/@jiwoon", "https://example.com/@mogu" ], "manuallyApprovesFollowers": true, "followers": "https://example.com/@busantravel/followers", "inbox": "https://example.com/@busantravel/inbox", "outbox": "https://example.com/@busantravel/outbox" }That is,
“The Group Actor itself is an independent profile, but it is jointly managed by multiple users (Actors).”
How to implement in WordPress in 3 steps
Define Group Actor
Extend the Actor Registry of the ActivityPub plugin (includes/class-activitypub-actor-factory.php)
to register a new actor with type: Group.
This way, within WordPress,
type: Person)type: Group)Both types coexist.
Connecting group profiles with actual users (setting shared ownership)
Link WordPress user IDs in the group actor’s metadata under moderators or attributedTo fields:
➡ This makes the group profile
a “shared account where multiple users can post together.”
Inbox/Outbox behavior of shared profile
This part is key.
By default, the ActivityPub plugin manages inbox/outbox by
/author/{user},but the Group Actor must have separate paths.
In this outbox, merge activities from multiple admins.
That is, posts from Jiwoon and thaumiel999 are both propagated to
/group/busantravel/outbox.(Override the get_actor() part of ActivityPub\Transformer\Post class to achieve this.)
🔐 Permission (Moderation) structure
➡ In WordPress, use the
capabilitiessystem (edit_posts,manage_options, etc.)to map these roles to actual admin permissions.
Create a custom capability such as
group_moderatorand restrict access withuser_can().🌐 Example result
For example,
Group profile: @[email protected]
Admins:
In this case:
/group/travel-in-busanThus, it becomes perfectly “a Facebook page operated by multiple admins.”
Advanced: clues already implemented at the ActivityPub plugin level
Blogtype actor.(
class-activitypub-blog.php)This is effectively “a shared profile representing the entire site.”
Group+ adding moderator linkage,it can be directly transformed into a “shared profile.”
This way,
@[email protected]acts as the official group actor of the entire WordPress blog,
allowing multiple accounts to publish under that name.
Summary
moderatorsorattributedTofield/group/{slug}/outbox👉 An integrated structure of Blog Actor + Group Actor is ideal.
That is,
@travel-in-busan→ Site’s representative shared profile (Group)@jiwoon,@mogu→ Individual Actors (Person)🧠 Basic conceptual overview
attributedTo/moderatorsThat is:
Forum = Group Actor
Topic = Activity (Create)
Reply = Activity (Reply)
Once this structure is established, each forum operates as an independent group actor connected with external instances.
① Register bbPress forums as ActivityPub Group Actors
By default, bbPress forums have
post_type = forum.So add a hook to automatically register these forums to the ActivityPub plugin’s Actor Factory.
This creates forum-level actor endpoints in WordPress such as:
/forum/travel-tips/inbox/forum/travel-tips/outboxThat is, on Fediverse,
functions as a single group actor (forum).
② Moderator/Admin connection (group actor managers)
Each forum in bbPress supports setting
Forum Moderator,so link this to the ActivityPub metadata’s
attributedToormoderatorsfield.➡ Thus, on Fediverse
is displayed, showing that both people jointly manage the forum group.
③ Convert Topics and Replies to Activities
post_type = topic) is created → send aCreateActivitypost_type = reply) is added → send anAnnounceorReplyActivityExample:
Thus, even on external instances (e.g., Mastodon, Misskey, Friendica, etc.),
new topics from that forum appear as “group posts.”
④ Federated forum subscription (Follow/Join)
Because it is a Group Actor, you can use the standard ActivityPub
Follow/Join/Announce.@[email protected]→ added to
followerslist→ it goes into
/forum/travel-tips/inboxand is saved as a comment in WordPressThat is, when a Mastodon user comments,
→ it automatically appears in the WordPress bbPress forum thread ✨
🏗️ Structure diagram
Each forum is independently exposed to Fediverse as a group,
and communities on different topics (travel, AI, design, etc.) are naturally separated.
💡 Application ideas
Like/DislikeActivitiesJoinactivity with bbPressSubscribeauthor-based actors are supported, so group/forum actors require customization✨ Summary
Thus, each forum functions as an independent Fediverse community as a group actor,
and you and co-administrators can control all of it with a single WordPress admin account.
https://socialhub.activitypub.rocks/t/where-and-how-to-send-join-activity/4263/2
⚙️ Premise
Personactor:@[email protected]Groupactor:@[email protected]🔁 1. Join Process
The local
Personactor creates aJoinActivity for the remoteGroupactor.{ "@context": "https://www.w3.org/ns/activitystreams", "type": "Join", "actor": "https://a.example/users/jiwoon", "object": "https://b.example/groups/forum", "to": ["https://b.example/groups/forum"] }This Activity is sent to the remote group’s Inbox.
That is →
POST https://b.example/inboxThe remote Group server verifies this
Joinrequest and, upon approval (or auto-approval), registers the Person as a group member.🗣️ 2. Post (Create) Process
Now let’s see when
@[email protected]posts to the group.The local server (A) generates a
CreateActivity.{ "@context": "https://www.w3.org/ns/activitystreams", "type": "Create", "actor": "https://a.example/users/jiwoon", "to": ["https://b.example/groups/forum"], "object": { "type": "Note", "content": "This is a post I made in the group forum." } }The key point here is
"to": ["https://b.example/groups/forum"]→ Since the recipient is the group actor, this Activity is sent to the remote Group’s Inbox.
That is, the post goes directly into b.example’s
/inbox.The Group server (B) checks whether the received Activity is addressed to itself,
→ copies it into the group’s outbox to publish it to the group feed,
→ and broadcasts it to group members (followers).
📦 3. Internal Operation of the Remote Group
While processing
Create, the remote server (B):to,cc)@[email protected]as the author@jiwoon, but hosting is handled by the group (B)🔄 4. Flow of Replies/Notifications
tofield of that comment includes the original author (@jiwoon),→ the remote group server (B) forwards it back to the local server (A)’s
inbox.→ In other words, bidirectional Activity flow occurs between servers.
💡 In Summary
→ Requires additional extension or custom implementation.
Accept,Reject,Moderation) varies by implementation.👉 Conclusion:
🧩 Key Summary
attributedTofieldThat is,
⚙️ Detailed Data Flow
① Activity creation on local server (A)
{ "@context": "https://www.w3.org/ns/activitystreams", "type": "Create", "actor": "https://a.example/users/jiwoon", "to": ["https://b.example/groups/forum"], "object": { "id": "https://a.example/users/jiwoon/posts/12345", "type": "Note", "attributedTo": "https://a.example/users/jiwoon", "content": "Hello, this is a group post.", "published": "2025-10-12T10:00:00Z" } }Here,
object.idis the original address on the local server (A).However, the remote group (B) processes this post and creates a “hosted copy.”
② Inbox processing on remote server (B)
When the remote group
@[email protected]receivesCreate, it performs the following:object.id— whether it’s externally hosted.object.idwith its own URL or keep canonical link).Thus, the actual storage is divided as follows:
https://a.example/users/jiwoon/posts/12345https://b.example/groups/forum/posts/9876📦 Example Storage Structure (Based on Lemmy or bbPress extension design)
→ The group server maintains the original URL (
canonical_url) while storing a replicated copy in its own DB.→ Therefore, if the group disappears, the post disappears too, and the author’s post “only existed within the group.”
🧠 Conceptual Summary
Default WordPress ActivityPub plugin structure does not yet support Object replication (“federated post hosting”).
→ That is, if the original Object is external, it is shown only as a simple link.
→ To use a Group model, you need an ActivityPub extension (e.g.
activitypub-forum,bbpress-activitypub-extension).Preventing Object ID duplication:
The Group server often issues a new internal ID instead of using the received Object’s ID directly.
Deletion (Undo/Delete) synchronization issue:
Even if the original author deletes the post, the replicated copy on the remote group often remains.
🔍 Summary
“When converting a bbPress forum into an ActivityPub-based Group, how to map the Object replication/storage structure to the WordPress database (postmeta-based)”
Example:
wp_posts↔activitypub_objects↔group_metastructure.Lifecycle of ActivityPub “Tombstone” Object — “When a deleted post is restored, can it be reverted back to Article/Object at the ActivityPub level?”
⚰️ 1. What is a Tombstone?
According to the ActivityPub spec (W3C Recommendation),
a
Tombstoneis an object type that acts as a ‘marker’ for a deleted object.{ "type": "Tombstone", "formerType": "Article", "id": "https://example.com/posts/123", "deleted": "2025-10-12T12:00:00Z" }That is:
idremains the same as the previous post ID.formerTypeindicates the pre-deletion type (Note,Article, etc.)deletedrecords the deletion time.💡 When this object appears,
followers or remote servers recognize “the Object with this ID is gone.”
(Usually the UI hides or marks it as “deleted.”)
🧟 2. Restoring a Tombstone (Theoretical Possibility)
The question is: Can the Tombstone be “revived”?
In the spec, a Tombstone is treated as a “permanent deletion marker,”
👉 so restoring the original Object as-is is not forbidden,
but you must propagate it as a new Create.
That is, instead of directly reverting the Tombstone:
This is closer to “reposting” than restoration.
That is, WordPress internally distinguishes between “trash” and “delete,”
but ActivityPub plugins usually do not —
when
post_status = trash, they just send a Delete Activity.Therefore, when restoring, you must manually send a new
CreateActivity.Practically Safe Restoration Procedure (Example for WordPress)
When
post_status = trash, ActivityPub publishes aDeleteActivity and generates a Tombstone.When the user clicks “Restore”:
post_statustopublish.CreateActivity.object.id.On remote servers, it appears as a new post, often overwriting the old one.
💡 Recommendation:
{ "type": "Article", "id": "https://example.com/posts/456", "replaces": "https://example.com/posts/123", "content": "This is the restored post." }“In the WordPress ActivityPub plugin, synchronize permanent deletion with Tombstone and re-propagate Create upon reposting (PHP filter + REST hook)”
To fully understand this, you need to know:
to,cc,audiencefields in ActivityPub🌐 1. Basic Concept: Visibility is valid only “at the time of distribution”
In ActivityPub, visibility is determined at the time the post is first sent (to/cc).
The important point here is:
Because ActivityPub is a federated broadcast system,
once a post is distributed, it’s stored in remote server databases,
and there’s no mechanism to “recall” or “re-broadcast” with modified visibility.
🎭 A. Using
UpdateActivityTo change the visibility, you have to think of it as “sending the modified Object again.”
Technically, this is not “visibility modification” but “replacing (Updating) the entire object.”
However, most servers do not update the visibility settings of the existing Object even when they receive an
Update.That is, it is possible in the standard, but it almost never works in practice.
🔒 B. “Private Repost (Re-create)” Pattern
In actual federated networks like Misskey, this method is used:
DeleteActivity for the existing post (processed as Tombstone in all instances)CreateActivity,specifying new
to/ccranges (e.g., Public → Followers)🧩 In short, delete + re-create is the safest method.
(This is in fact the de facto standard convention across the entire Fediverse.)
🧭 3. Behavior in the WordPress ActivityPub Plugin
In WordPress, “visibility” is stored as
post_statusorvisibilitymeta.The default plugin maps this to the
tofield of ActivityPub:publicPublic+ followersprivatedraft/trashWhen this state is changed,
That is,
Why ActivityPub Does Not Support “Visibility Changes”
ActivityPub is fundamentally an event-based, immutable log structure.
It has no function to “edit a previously sent Activity,”
and must always represent the state through a new Activity.
Therefore, visibility changes are “delete and recreate.”
Through this:
📌 Conclusion
Updateonly works in certain implementations (WordPress, Friendica, some Pleroma)The concept of “default posts page → Organization actor, custom post type: forum → Group actor”
is a very logical design that naturally maps the WordPress content structure to the Fediverse concept.
However, across the ActivityPub network, the
Organizationtype is almost a “theoretical species that exists in the spec but is rarely used in practice.”Let’s go through why that is, how it can be complemented, and how to make it feasible in WordPress step by step.
🧩 1. Why Your Model Is Valid
Posts Page(blog root)Author(individual writer)Custom Post Type: forumCommentorreplyThis mapping exactly matches the Fediverse social hierarchy of “site = institution, forum = community, user = member.”
That is,
This structure is far richer than the single
Person-centric model of Mastodon or Misskey.🧩 2. Why “Organization” Is Not Actually Used
🚫 1) Mastodon / Misskey / Lemmy / PeerTube etc. only implement
PersonorServiceAlthough the ActivityPub spec defines the following Actor types:
PersonGroupOrganizationServiceApplicationIn actual implementations, only
Personis generally supported.For example, in the Actor JSON of Mastodon:
Misskey is the same.
Organizationis practically a “dead spec” — it exists in the standard but most software does not recognize it.This is because the Fediverse has long been centered on “individual-based microblogging.”
🧩 3. In Practice, Organization Has Been Replaced by Service / Application
Serviceactors.Organization,but for federation compatibility, it must be treated as
Service.That is, while
Organizationis technically correct in the spec,using
Servicetype is the realistic compromise to ensure interoperability.{ "@context": "https://www.w3.org/ns/activitystreams", "type": "Service", "id": "https://travel-in-busan.com/blog", "name": "Travel in Busan Blog", "preferredUsername": "blog", "inbox": "https://travel-in-busan.com/blog/inbox", "outbox": "https://travel-in-busan.com/blog/outbox" }Even with this, most Fediverse clients display it normally.
(It appears as an account called “Blog.”)
🧩 4. Mapping Group Actor to Forum CPT Is Perfectly Valid
In a WordPress + bbPress environment, a forum has the following relationships:
This matches exactly with ActivityPub’s structure:
Thus, a forum can be treated as a “group per board” in federation.
For example:
{ "type": "Group", "id": "https://travel-in-busan.com/forums/busan-food", "name": "Busan Food Forum", "inbox": "https://travel-in-busan.com/forums/busan-food/inbox", "members": [ "https://travel-in-busan.com/users/jiwoon", "https://mastodon.social/users/alice" ] }Users can then join this group with a
Joinactivityand post within it via
Create.🧩 5. Why an Organization Actor Is Necessary
Treating an entire site as a single
Personcauses issues.PersonsTherefore, designating the entire WordPress blog as an
Organization(orService) provides:🧩 6. Suggested Implementation Direction
✅ 1) Register posts page as Service actor
→ This makes the “site itself” function as an independent sender in federation.
✅ 2) Register forum CPT as Group actor
→ Creates an independent group actor per forum
→ Remote users can follow or join each forum
✅ 3) Person → actual user (author)
This is already provided by the existing WordPress ActivityPub plugin.
Thus, the site structure expands as follows:
This makes the hierarchy “users belonging to forum groups within this blog” clearly visible across the federation.
🧩 7. Final Summary
Service(technicallyOrganization, useServicefor compatibility)GroupPersonApplication💡 Conclusion
Create an example of “implementing a structure in the WordPress ActivityPub plugin where Organization (Service) + Group + Person are simultaneously registered” as
functions.phpor a standalone plugin.Beta Was this translation helpful? Give feedback.
All reactions