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_meta
to 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_meta
or 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
Group
type actor exists and is structurally defined as follows: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
capabilities
system (edit_posts
,manage_options
, etc.)to map these roles to actual admin permissions.
Create a custom capability such as
group_moderator
and restrict access withuser_can()
.🌐 Example result
For example,
Group profile: @[email protected]
Admins:
In this case:
/group/travel-in-busan
Thus, it becomes perfectly “a Facebook page operated by multiple admins.”
Advanced: clues already implemented at the ActivityPub plugin level
Blog
type 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
moderators
orattributedTo
field/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
/moderators
That 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/outbox
That 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
attributedTo
ormoderators
field.➡ 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 aCreate
Activitypost_type = reply
) is added → send anAnnounce
orReply
ActivityExample:
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
followers
list→ it goes into
/forum/travel-tips/inbox
and 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
/Dislike
ActivitiesJoin
activity with bbPressSubscribe
author
-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
Person
actor:@[email protected]
Group
actor:@[email protected]
🔁 1. Join Process
The local
Person
actor creates aJoin
Activity for the remoteGroup
actor.This Activity is sent to the remote group’s Inbox.
That is →
POST https://b.example/inbox
The remote Group server verifies this
Join
request 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
Create
Activity.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
to
field 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
attributedTo
fieldThat is,
⚙️ Detailed Data Flow
① Activity creation on local server (A)
Here,
object.id
is 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.id
with its own URL or keep canonical link).Thus, the actual storage is divided as follows:
https://a.example/users/jiwoon/posts/12345
https://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_meta
structure.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
Tombstone
is an object type that acts as a ‘marker’ for a deleted object.That is:
id
remains the same as the previous post ID.formerType
indicates the pre-deletion type (Note
,Article
, etc.)deleted
records 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
Create
Activity.Practically Safe Restoration Procedure (Example for WordPress)
When
post_status = trash
, ActivityPub publishes aDelete
Activity and generates a Tombstone.When the user clicks “Restore”:
post_status
topublish
.Create
Activity.object.id
.On remote servers, it appears as a new post, often overwriting the old one.
💡 Recommendation:
“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
,audience
fields 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
Update
ActivityTo 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:
Delete
Activity for the existing post (processed as Tombstone in all instances)Create
Activity,specifying new
to
/cc
ranges (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_status
orvisibility
meta.The default plugin maps this to the
to
field of ActivityPub:public
Public
+ followersprivate
draft
/trash
When 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
Update
only 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
Organization
type 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: forum
Comment
orreply
This 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
Person
orService
Although the ActivityPub spec defines the following Actor types:
Person
Group
Organization
Service
Application
In actual implementations, only
Person
is generally supported.For example, in the Actor JSON of Mastodon:
Misskey is the same.
Organization
is 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
Service
actors.Organization
,but for federation compatibility, it must be treated as
Service
.That is, while
Organization
is technically correct in the spec,using
Service
type is the realistic compromise to ensure interoperability.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:
Users can then join this group with a
Join
activityand post within it via
Create
.🧩 5. Why an Organization Actor Is Necessary
Treating an entire site as a single
Person
causes issues.Person
sTherefore, 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
, useService
for compatibility)Group
Person
Application
💡 Conclusion
Create an example of “implementing a structure in the WordPress ActivityPub plugin where Organization (Service) + Group + Person are simultaneously registered” as
functions.php
or a standalone plugin.Beta Was this translation helpful? Give feedback.
All reactions