WP Manifestindependent plugin directory
manifest / integrations / tiaa-wpplugin

TIAA WordPress Plugin

WordPress plugin in support of tiaa-forum.org

by Lew Grothe and TIAA Forum Admin Platform sub-team. · github.com/tiaa-forum-org/tiaa-wpplugin · website

0stars
1release downloads
0forks

Install

The author publishes release zips, so WP-CLI can install straight from GitHub:

wp plugin install https://github.com/tiaa-forum-org/tiaa-wpplugin/releases/download/v0.0.19/tiaa-wpplugin.zip

Overview

The TIAA WordPress Plugin (from tiaa-forum.org) is designed to provide essential functionality for the TIAA-Forum.org community, integrating key features to enhance user experience, streamline administrative processes, and improve interactions with external the Discourse server.

This plugin consolidates various functions previously handled by Google Apps Scripts and third-party WordPress plugins, aiming for a more maintainable and WordPress-native solution.

Features

1. User Invitation Management

  • Replaces the existing Google Apps Script-based invitation system.
  • Provides a form-based invite process within WordPress.
  • Integrates with Discourse to automate invitation processing.
  • Allows invitation to a particular Discourse group to allow for special onboarding or to allow changing the home group for members of a particular primary group
  • Detects duplicate email addresses and directs users to the password reset process.

Setting up an Invite Topic ID

Both the Signup tab and each Group Invite tab have an optional Topic ID field. When set, a new member lands on that Discourse topic right after accepting their invite — it's typically used as a "start here" post tailored to that particular invite group.

Why the topic has to be created a specific way, not just any topic ID: Discourse's invite API checks whether the linked topic is visible as if the recipient hadn't joined yet — regardless of how privileged the account sending the invite is. If the topic lives in a category that isn't readable by "Everyone" (for example, one restricted to logged-in members only), every invite send using that Topic ID fails, even though the exact same topic links without any problem when an admin creates the invite manually from inside Discourse's own web UI (confirmed 2026-08-20 — that flow doesn't go through the same check the API does).

Process for creating a topic to use as an invite Topic ID (confirmed working 2026-08-20):

  1. On Discourse, create a new topic in the #public category.
  2. Write the post as you'd want a new invite recipient to see it — this is the first thing they'll land on after joining.
  3. Once the post is finished, open the topic's settings and change it to to unlisted.
  4. Note the topic ID from the URL — that's the value to enter in the Topic ID field on the Signup or Group Invite settings tab in the topic_id field.

Creating the topic in #public first, then moving it to unlisted, keeps the category's read permission set to "Everyone" — satisfying the invite API's visibility check above — while unlisted hides the topic from public search, the topic list, and ordinary browsing (unless the user is a member of the staff group). In practice this means the topic is only reachable by someone who has the direct link (as the invite itself provides), even though it isn't gated by a logged-in-only permission the way a normal restricted category would be.

Setting up the invite form's honeypot field

The /invite REST endpoint is intentionally public (anonymous visitors submit it), which makes it a target for form-scraping spam bots. As one layer of defense, the plugin checks for a hidden honeypot field named website on the submitted form — see TiaaHooks::INVITE_HONEYPOT_FIELD's docblock for the full reasoning. This field has to be added on the Elementor side; it can't be added by a code change alone.

Do not use Elementor Pro's built-in "Honeypot" field type. Checked 2026-08-27: that field type renders with a hardcoded style="display:none !important" attribute, with no way to style it differently. display:none is the textbook honeypot tell — it's exactly what a honeypot-aware scraper checks for and skips filling, which defeats the purpose entirely. Use a regular text field instead, hidden by positioning it off-screen (see step 2 below) rather than hiding it via display:none — that doesn't carry the same widely-recognized fingerprint.

To set it up, on each Elementor invite form (Signup and every Group Invite form):

  1. Add a new text field to the form with the exact field ID/name website (or change TiaaHooks::INVITE_HONEYPOT_FIELD to match a different name — just keep the two in sync).
  2. In the field's Advanced settings, add a custom CSS class (e.g. tiaa-hp), then add this rule to the site's custom CSS:
    .tiaa-hp { position: absolute; left: -9999px; top: -9999px; }

    Use position: absolute off-screen, not display: none — some bots skip fields hidden that way but still fill anything present in the DOM regardless of CSS visibility.

  3. Leave the field optional/unvalidated in Elementor — a real visitor should never interact with it at all, and its presence with any value is what marks a submission as a bot.

A real visitor never sees or fills this field, so it stays empty on every genuine submission. A submission that arrives with it populated is treated as a bot and gets a normal-looking success response with no Discourse API call made — see the honeypot section of TiaaHooks::invite_to_discourse() for why the response is disguised rather than an explicit rejection.

Limitation: this only screens naive, generic scraping bots that blindly fill every field they find. It does not stop a targeted attacker who inspects the real request and simply omits the field — the per-email invite rate limit (TiaaHooks::INVITE_EMAIL_RATE_LIMIT_MAX) is the defense that still holds against that case.

2. Welcome Message Automation

  • Sends personalized Discourse messages to new users.
  • Helps onboard members by explaining key features of the forum.
  • Ensures engagement and retention by encouraging participation.

3. Site Settings

  • Centralized settings page for site-wide configuration (Admin > TIAA Forum > Site Settings).
Setting Purpose
Cookie Domain Shared domain used for cross-subdomain cookies between WordPress and Discourse (e.g. .tiaa-forum.org for production). Can be locked via the TIAA_COOKIE_DOMAIN constant in wp-config.php to prevent accidental changes.
Contact Email Site contact email address. Displayed anywhere via [tiaa_contact_email], rendered as a mailto: link.
Funding Level Reserve status (blue / green / yellow / red) — colors for the "Contribute" button on the front end header.
Forum Stats Member, topic, post, and category counts, plus an as-of date. Updated manually and displayed anywhere (e.g. Home Page) via [tiaa_stat field="..."] shortcodes. Current numbers can be obtained via a Discourse Data Explorer plugin query.
  • Also drives front-end automations that update automatically when a Site Setting changes — no code or template edits needed:
    • Funding Level → colors the .tiaa-contribute button (Elementor CSS class) via an inline `

Releases

1 release. Each count is every asset in that release; expand a row for the breakdown.

Tag
Published
Assets
Downloads
v0.0.19 latest
Aug 21, 2026 1mo ago
tiaa-wpplugin.zip
1