[buddypress-trac] [BuddyPress Trac] #9327: Enhancement Request: Add `privacy` column to `bp_activity` for per-item visibility control (was: Feature Request: Add `privacy` column to `bp_activity` for per-item visibility control)
buddypress-trac
noreply at wordpress.org
Fri Feb 6 02:24:54 UTC 2026
#9327: Enhancement Request: Add `privacy` column to `bp_activity` for per-item
visibility control
-------------------------+------------------------------
Reporter: indigetal | Owner: (none)
Type: enhancement | Status: new
Priority: normal | Milestone: Awaiting Review
Component: Activity | Version:
Severity: normal | Resolution:
Keywords: |
-------------------------+------------------------------
Changes (by emaralive):
* version: 14.4.0 =>
* component: Core => Activity
Old description:
> #### The Need
>
> BuddyPress activities have a single visibility control: `hide_sitewide`
> (0 or 1). This provides binary visibility — shown everywhere, or hidden
> from sitewide feeds. There's no way to express:
>
> - **"Only me"** — private activity visible only to the author
> - **"Friends only"** — visible only to the author's friends
> - **"Logged-in users"** — hidden from anonymous visitors
> - **"Group members only"** — visible only within the group context where
> it was posted
>
> Per-item privacy is a foundational social platform feature. Facebook has
> had it since 2009. LinkedIn, Twitter/X (with protected tweets),
> Instagram, and every major social network support some form of per-post
> audience control. It's as fundamental to a social platform as
> `hide_sitewide` is — just multi-valued instead of binary.
>
> This is not a request for any specific addon's needs. It's a request for
> the same kind of infrastructure that `hide_sitewide` provides — a first-
> class column that any addon can use to implement privacy-aware features.
>
> #### Current State
>
> BuddyPress 14.4.0's activity query system is well-designed for
> extensibility:
>
> - `bp_activity_get_where_conditions` lets addons inject WHERE clauses
> - `bp_activity_get_join_sql` lets addons add JOINs
> - Data hydration uses `SELECT *` (in `populate()` and the cache-miss path
> of `get()`), so any column added to the table is automatically returned
> to calling code
> - `bp_activity_before_save` / `bp_activity_after_save` provide save-time
> hooks
>
> An addon **can** add a `privacy` column via `dbDelta` and filter queries
> via `bp_activity_get_where_conditions`. However, the `save()` method has
> a hardcoded column list in its INSERT/UPDATE SQL (in
> `BP_Activity_Activity::save()`), meaning an addon must do a separate
> `$wpdb->update()` after every save — a double-write on every activity
> creation. This works but is suboptimal, and the column's general-purpose
> nature (used by media addons, document addons, moderation addons, group
> addons — not just one feature) argues for core inclusion.
>
> #### Proposed Change
>
> **Schema:** Add one column + index to `bp_activity` in
> `bp_core_install_activity_streams()` (in `bp-core-admin-schema.php`).
> `dbDelta()` handles adding the column on upgrade automatically:
>
> ```sql
> -- Added after the existing `is_spam` column in the CREATE TABLE
> definition:
> privacy varchar(75) NOT NULL DEFAULT 'public',
> -- Added to the KEY list:
> KEY privacy (privacy)
> ```
>
> **Model class (`BP_Activity_Activity`):**
>
> 1. Add `public $privacy = 'public';` property
> 2. In `save()` — add `privacy` to the INSERT/UPDATE column list alongside
> the existing columns (user_id, component, type, action, content, etc.)
> 3. In `get()` — add `'privacy'` to the `$r` defaults (default `false`,
> meaning no filtering). When set, add `AND a.privacy IN (...)` to the
> WHERE conditions.
>
> **REST API (`BP_REST_Activity_Endpoint`):**
>
> 4. In `prepare_item_for_response()` — expose `privacy` in the response
> object (paralleling how `hide_sitewide` is currently exposed as `hidden`)
> 5. In `prepare_item_for_database()` — accept `privacy` as a writable
> field on create/update
>
> **Migration:** `dbDelta()` runs on every upgrade via `bp_core_install()`,
> so modifying the CREATE TABLE definition is sufficient for the schema
> change. A DB version bump in `bp-core-update.php` tracks the upgrade.
>
> #### Scope
>
> - ~40 lines of changes across 4 files (`bp-core-admin-schema.php`,
> `class-bp-activity-activity.php`, `class-bp-rest-activity-endpoint.php`,
> `bp-core-update.php`)
> - Default value `'public'` — 100% backward compatible
> - Existing queries that don't pass `privacy` return all activities as
> before
> - `hide_sitewide` continues to work as-is (orthogonal — `hide_sitewide`
> controls directory listing, `privacy` controls per-item access)
>
> #### What Core Provides vs. What Addons Handle
>
> To be explicit about scope: this proposal adds **storage and query
> filtering only**. Core would:
>
> - Store the `privacy` value on each activity row
> - Allow `get()` callers to filter by privacy value(s)
> - Expose/accept the field through the REST API
>
> Core would **not** need to:
>
> - Define a fixed set of allowed values (addons register the values they
> need — e.g., `'onlyme'`, `'friends'`, `'loggedin'`)
> - Enforce access control based on privacy values (addons hook into
> `bp_activity_get_where_conditions` to inject viewer-aware WHERE clauses,
> exactly as they would today for any custom filtering)
> - Provide UI for selecting privacy (addon/theme territory)
>
> This mirrors how `hide_sitewide` works today — core stores the flag and
> filters on it; the decision of *when* to set it is made by callers
> (groups component, plugins, etc.).
>
> #### Prior Art
>
> BuddyBoss Platform (a BuddyPress-derived platform) implemented this exact
> column. Their `bp_activity` schema includes `privacy varchar(75) NOT NULL
> DEFAULT 'public'`, and their media, document, video, and moderation
> subsystems all depend on it for privacy filtering. The column is
> referenced in their activity save/query paths, REST API, and template
> layer — making it one of the most cross-cutting additions they made to
> the activity table. The fact that an independent platform needed this to
> build standard social features demonstrates both the demand and the
> general-purpose nature of the column.
>
> I'm happy to submit a PR for this change.
New description:
== The Need
!BuddyPress activities have a single visibility control: `hide_sitewide`
(0 or 1). This provides binary visibility — shown everywhere, or hidden
from sitewide feeds. There's no way to express:
- **"Only me"** — private activity visible only to the author
- **"Friends only"** — visible only to the author's friends
- **"Logged-in users"** — hidden from anonymous visitors
- **"Group members only"** — visible only within the group context where
it was posted
Per-item privacy is a foundational social platform feature. Facebook has
had it since 2009. LinkedIn, Twitter/X (with protected tweets), Instagram,
and every major social network support some form of per-post audience
control. It's as fundamental to a social platform as `hide_sitewide` is —
just multi-valued instead of binary.
This is not a request for any specific addon's needs. It's a request for
the same kind of infrastructure that `hide_sitewide` provides — a first-
class column that any addon can use to implement privacy-aware features.
== Current State
!BuddyPress 14.4.0's activity query system is well-designed for
extensibility:
- `bp_activity_get_where_conditions` lets addons inject WHERE clauses
- `bp_activity_get_join_sql` lets addons add JOINs
- Data hydration uses `SELECT *` (in `populate()` and the cache-miss path
of `get()`), so any column added to the table is automatically returned to
calling code
- `bp_activity_before_save` / `bp_activity_after_save` provide save-time
hooks
An addon **can** add a `privacy` column via `dbDelta` and filter queries
via `bp_activity_get_where_conditions`. However, the `save()` method has a
hardcoded column list in its INSERT/UPDATE SQL (in
`BP_Activity_Activity::save()`), meaning an addon must do a separate
`$wpdb->update()` after every save — a double-write on every activity
creation. This works but is suboptimal, and the column's general-purpose
nature (used by media addons, document addons, moderation addons, group
addons — not just one feature) argues for core inclusion.
== Proposed Change
**Schema:** Add one column + index to `bp_activity` in
`bp_core_install_activity_streams()` (in `bp-core-admin-schema.php`).
`dbDelta()` handles adding the column on upgrade automatically:
=== sql
{{{
-- Added after the existing `is_spam` column in the CREATE TABLE
definition:
privacy varchar(75) NOT NULL DEFAULT 'public',
-- Added to the KEY list:
KEY privacy (privacy)
}}}
**Model class (`BP_Activity_Activity`):**
1. Add `public $privacy = 'public';` property
2. In `save()` — add `privacy` to the INSERT/UPDATE column list alongside
the existing columns (user_id, component, type, action, content, etc.)
3. In `get()` — add `'privacy'` to the `$r` defaults (default `false`,
meaning no filtering). When set, add `AND a.privacy IN (...)` to the WHERE
conditions.
**REST API (`BP_REST_Activity_Endpoint`):**
4. In `prepare_item_for_response()` — expose `privacy` in the response
object (paralleling how `hide_sitewide` is currently exposed as `hidden`)
5. In `prepare_item_for_database()` — accept `privacy` as a writable field
on create/update
**Migration:** `dbDelta()` runs on every upgrade via `bp_core_install()`,
so modifying the CREATE TABLE definition is sufficient for the schema
change. A DB version bump in `bp-core-update.php` tracks the upgrade.
== Scope
- ~40 lines of changes across 4 files (`bp-core-admin-schema.php`, `class-
bp-activity-activity.php`, `class-bp-rest-activity-endpoint.php`, `bp-
core-update.php`)
- Default value `'public'` — 100% backward compatible
- Existing queries that don't pass `privacy` return all activities as
before
- `hide_sitewide` continues to work as-is (orthogonal — `hide_sitewide`
controls directory listing, `privacy` controls per-item access)
== What Core Provides vs. What Addons Handle
To be explicit about scope: this proposal adds **storage and query
filtering only**. Core would:
- Store the `privacy` value on each activity row
- Allow `get()` callers to filter by privacy value(s)
- Expose/accept the field through the REST API
Core would **not** need to:
- Define a fixed set of allowed values (addons register the values they
need — e.g., `'onlyme'`, `'friends'`, `'loggedin'`)
- Enforce access control based on privacy values (addons hook into
`bp_activity_get_where_conditions` to inject viewer-aware WHERE clauses,
exactly as they would today for any custom filtering)
- Provide UI for selecting privacy (addon/theme territory)
This mirrors how `hide_sitewide` works today — core stores the flag and
filters on it; the decision of *when* to set it is made by callers (groups
component, plugins, etc.).
== Prior Art
BuddyBoss Platform (a !BuddyPress-derived platform) implemented this exact
column. Their `bp_activity` schema includes `privacy varchar(75) NOT NULL
DEFAULT 'public'`, and their media, document, video, and moderation
subsystems all depend on it for privacy filtering. The column is
referenced in their activity save/query paths, REST API, and template
layer — making it one of the most cross-cutting additions they made to the
activity table. The fact that an independent platform needed this to build
standard social features demonstrates both the demand and the general-
purpose nature of the column.
I'm happy to submit a PR for this change.
--
Comment:
[[span(style=color: #FF0000, Edited the description for readability.
Slightly modified the Summary. )]]
For reference, this started as a support forum topic, see
[https://buddypress.org/support/topic/add-privacy-column-to-bp_activity-
for-per-item-visibility-control/ Add `privacy` column to `bp_activity` for
per-item visibility control]
--
Ticket URL: <https://buddypress.trac.wordpress.org/ticket/9327#comment:1>
BuddyPress Trac <http://buddypress.org/>
BuddyPress Trac
More information about the buddypress-trac
mailing list