Wbcom Designs Jetonomy Docs
Back to product Buy Now

Getting Started

Install, configure, and launch your community in minutes.

Feature Overview

Everything Jetonomy does, on one page. This is the fastest way to see whether Jetonomy fits your community - it groups every feature by what it actually does for you, links straight to the deep-dive guide for each area, and marks which features are free and which are Jetonomy Pro.

The Jetonomy community home page showing categories, spaces, and recent activity

How to Use This Page

Each section below is a job your community needs done - publishing discussions, organizing them, keeping them clean, recognizing contributors, and so on. Read the benefit, scan what is included, then follow the links to the full guide for anything you want to set up.

Features marked Pro require Jetonomy Pro. Everything else ships in the free plugin.

Content & Discussions

This is the heart of Jetonomy: a fast, modern composer and a reply system built for threads with hundreds of contributions. Members write clear topics, the best answers float to the top, and nobody loses a half-finished post.

What is included:

  • Topics - the primary unit of discussion, with a Markdown composer, inline image upload, tags, similar-topic detection, and topic prefixes. See Creating Topics.
  • Replies and threading - threaded conversations, multiple sort orders, and efficient loading for long threads. See Replies & Threading.
  • Voting - upvote and downvote on posts and replies so the most useful content rises naturally. See Voting.
  • Accepted answers - mark the reply that solved a question in Q&A spaces. See Replies & Threading.
  • Drafts and scheduling - save work in progress and publish now or at a future date. See Drafts & Scheduling.

Pro adds:

  • Polls - attach a single or multi-select poll to any topic for decisions and feedback. See Polls.
  • Reactions - emoji reactions on posts and replies so members can respond without writing a full reply. See Reactions.
  • Anonymous Posting - members post and reply without showing their name, enabled per space, with an audited admin-only reveal. See Anonymous Posting.
  • File Attachments - attach new images, PDFs, and documents to topics and replies, with a lightbox and an inline PDF viewer. (The free plugin already displays any attachment already on a post - see File Attachments.)

Spaces & Organization

Spaces are how you keep a large community navigable. Group them into categories, pick the right space type for each purpose, and control exactly who can join. The right structure makes your community feel purpose-built instead of generic.

What is included:

  • Categories and spaces - top-level categories that group focused discussion spaces. See Creating Spaces.
  • Four space types - Forum, Q&A, Ideas, and Feed, each changing how posts and replies behave. See Space Types.
  • Membership and join policies - public, private, or hidden visibility paired with open, approval-required, or invite-only join rules. See Membership & Join Policies.
  • Invites - invite-link landing pages so the right people get in without manual setup. See Membership & Join Policies.

Pro adds:

  • Custom Fields - structured fields on posts, member profiles, and spaces so you collect exactly the information your community needs. See Custom Fields.

Moderation & Trust

Jetonomy keeps quality high without a moderator watching every post. Trust grows automatically with good participation, members flag problems, and one queue handles everything that needs human review.

What is included:

  • Trust levels 0 to 5 - members earn higher trust and gain capabilities automatically as they contribute. See Trust Levels.
  • Flagging and reporting - any logged-in member can report content that breaks your rules. See Flagging & Reporting.
  • Moderation queue - a single dashboard for posts awaiting approval, flagged content, and spam-caught items. See Moderation Queue.
  • Anti-spam - multiple silent layers of spam protection that real members never notice. See Anti-Spam.
  • Banning - remove disruptive members from your community. See Trust Levels.
  • Blocking - members can hide a disruptive member's replies from their own view and report them in one step, from the mobile app or the REST API. See Flagging & Reporting.
  • Pinning and announcements - keep important topics at the top of a space. See Topic Management.

Pro adds:

  • Advanced Moderation - rules that catch bad content automatically before it ever appears. See Advanced Moderation.
  • Site Announcements - feature one post at the top of every space across the whole community. See Site Announcements.

Members & Gamification

Recognition keeps people coming back. Every member gets a public profile, reputation built from real contributions, and a place on the leaderboard worth competing for.

What is included:

  • Member profiles - a public page showing who a member is and what they have contributed. See User Profiles.
  • Reputation - a contribution score that powers trust levels and rankings. See User Profiles.
  • Leaderboard - public recognition for your top contributors. See Leaderboard.
  • Account deletion - members can permanently delete their own account from the mobile app or the REST API. See User Profiles.

Pro adds:

  • Custom Badges - design your own badges and the conditions members earn them by. See Custom Badges.

Search & Discovery

Members can only engage with content they can find. Full-text search and cross-space tags make every discussion discoverable the moment it is published.

What is included:

  • Full-text search - fast search across topics and replies with filters. See Search & Filters.
  • Tags - connect related discussions across every space from a single tag page. See Tags.

Pro adds:

  • SEO Pro - per-space meta titles, Open Graph images, Twitter Cards, schema markup, and sitemap rules. See SEO Pro.

Notifications & Messaging

Jetonomy brings members back when something relevant happens. Every event has an in-app notification and an email, and Pro adds direct messaging plus the channels members actually check.

What is included:

  • In-app notifications - real-time alerts for replies, mentions, votes, and more. See Notifications.
  • Email notifications - an email for every event type, with per-member control over what is delivered. See Email Settings.

Pro adds:

  • Private Messaging - one-on-one and small-group direct messages without leaving your community. See Private Messaging.
  • Email Digest - a curated daily or weekly summary of community activity. See Email Digest.
  • Web Push - browser push notifications that reach members even after they close your site. See Web Push.
  • Reply by Email - members reply to discussions straight from their email client. See Reply by Email.

Admin & Branding

Run the whole community from a clean WordPress admin. Every setting has a home, every action is logged, and Pro lets you present the community as entirely your own.

What is included:

  • Settings - configure general behavior, permissions, email, appearance, SEO, anti-spam, and access control. See General Settings.
  • Admin pages - manage spaces, categories, content, and users from the dashboard. See General Settings.
  • Activity log - a read-only, filterable audit trail of every important event. See Activity Log.

Pro adds:

  • White Label - remove all Jetonomy branding and present the community as your own product. See White Label.

Analytics & Integrations

See what your community is doing and connect it to the tools you already run. Jetonomy integrates with the major membership, commerce, learning, and community plugins so access follows enrollment automatically.

Pro adds:

  • Analytics - a single dashboard for growth, engagement, and your most active members. See Analytics.
  • Webhooks - send community events to Slack, Zapier, your CRM, or any custom pipeline. See Webhooks.

Integrations included:

  • MemberPress - gate spaces by membership level. See MemberPress.
  • Paid Memberships Pro - gate spaces by PMPro level. See Paid Memberships Pro.
  • WooCommerce - open spaces on product purchase or active subscription. See WooCommerce.
  • LearnDash - pair course and group enrollment with discussion spaces. See LearnDash.
  • Tutor LMS - pair course enrollment with discussion spaces. See Tutor LMS.
  • BuddyPress - make groups and forum spaces feel like one membership. See BuddyPress.
  • FluentCommunity - run the social feed and the forum as one product. See FluentCommunity.

Developer Platform

Jetonomy is built to be extended. Everything members can do is reachable through a documented REST API, hooks, WP-CLI, blocks, and shortcodes - so you can automate, embed, and customize without touching core.

What is included:

  • REST API - 48+ free endpoints under jetonomy/v1, 90+ with Pro active. See REST API.
  • Hooks - 58 free hooks plus 9 in Pro for actions and filters. See Hooks Reference.
  • WP-CLI - 14 free command roots and 15 in Pro for headless automation. See WP-CLI.
  • Blocks and shortcodes - eight Gutenberg blocks, four classic widgets, and eight shortcodes to embed community content anywhere. The four widgets are Recent Posts, Leaderboard, Active Spaces, and User Stats (Active Spaces and User Stats are reachable only through the [jetonomy_widget] shortcode). Most surfaces are available as both a block and a shortcode, with three exceptions each way: Navigation, Login, and User Panel are block-only, while [jetonomy_user_profile], [jetonomy_space_members], and [jetonomy_widget] are shortcode-only. See Shortcodes, Widgets & Blocks.

Pro adds:

  • AI - bring large language models into spam detection, auto-moderation, reply suggestions, and thread summaries, with self-hosted options. See AI.

What's Next?

Ready to set it up? Start with installation, then run the setup wizard to go live.

Installation

Get Jetonomy running on your WordPress site in under five minutes. This guide covers system requirements, how to install, and what happens the moment you activate.

Jetonomy setup wizard guiding you through initial configuration

What You Will Learn

  • Whether your server meets the requirements
  • Three ways to install Jetonomy
  • What Jetonomy sets up automatically on first activation

See it running first - community.wbcomdesigns.com is Wbcom's own support community, running Jetonomy in production. Browse the spaces, read the threads, and see how topics, replies, voting, trust levels, and moderation feel on a live site before you install. Public registration is open, so you can sign up and ask a question there if you have one.

Requirements

Jetonomy requires a modern WordPress stack. Check these before installing.

Requirement Minimum
WordPress 6.7 or higher
PHP 8.1 or higher
MySQL 5.7 or higher (or MariaDB 10.4+)
Browser Any modern browser (Chrome, Firefox, Safari, Edge)

Jetonomy works with any WordPress theme. For the best visual result with zero extra configuration, use BuddyX.

Note: Jetonomy does not use WordPress custom post types. It stores all community data in its own optimized database tables (wp_jt_*). This is intentional - it gives your community the query performance and scalability that CPT-based plugins cannot match.

Installation

  1. Go to Plugins → Add New Plugin in your WordPress admin.
  2. Search for Jetonomy.
  3. Click Install Now, then Activate.

Method 2: Upload a ZIP File

  1. Download the Jetonomy ZIP from jetonomy.com or wordpress.org.
  2. Go to Plugins → Add New Plugin → Upload Plugin.
  3. Choose the ZIP file and click Install Now, then Activate.

Method 3: WP-CLI

wp plugin install jetonomy --activate

What Happens on Activation

Jetonomy sets everything up automatically the first time you activate it. You do not need to run any SQL or configure anything manually.

Database tables created:

Jetonomy creates its own set of custom tables under the wp_jt_ prefix - one for each data entity: categories, spaces, posts, replies, votes, user profiles, notifications, subscriptions, tags, moderation flags, revisions, invite links, bookmarks, and more. It does not use WordPress custom post types.

WordPress capabilities registered:

Jetonomy registers its own custom capabilities (jetonomy_read, jetonomy_create_posts, jetonomy_moderate, and others) and maps them to your existing WordPress roles automatically.

Permalink rules flushed:

Your community URLs (e.g. yoursite.com/community/) are registered and rewrite rules are flushed immediately. No manual permalink reset needed.

URL Note: Throughout this documentation, /community/ is used as the default base URL. You can change this to any slug (e.g. /forum/, /discuss/, /hub/) in Jetonomy → Settings → General → Community Base URL. All community URLs automatically update when you change the base slug.

Cron jobs scheduled:

Six background jobs are scheduled via WP-Cron:

  • Trust level evaluation - promotes members who have earned higher trust levels.
  • Expired restriction cleanup - lifts temporary restrictions once they expire.
  • Activity log pruning - trims old activity-log rows to keep the table lean.
  • Notification cleanup - removes stale read notifications.
  • Scheduled post publishing - publishes posts queued for a future date.
  • Verification reminders - nudges members who have not completed verification.

After activation, you will see a blue notice at the top of your dashboard:

Your community is almost ready. Run the setup wizard to get started.

Click that notice to launch the setup wizard and go live.

Tip: If you are migrating from bbPress, wpForo, or Asgaros Forum, activate Jetonomy first to complete setup, then use the importer at Jetonomy → Import. Your existing data is never touched until you explicitly start an import.

WordPress Multisite

Jetonomy is multisite-compatible. When you network-activate the plugin from Network Admin → Plugins, Jetonomy automatically creates all required wp_jt_* database tables on every existing subsite in the network.

New subsites created after network activation are also provisioned automatically. The moment WordPress adds a new subsite to the network, Jetonomy detects it and creates the required tables before any community activity can occur.

Note: Each subsite has its own independent set of wp_{blog_id}_jt_* tables and its own community data. Spaces, posts, and members are not shared across subsites.

If you prefer per-site activation rather than network activation, install and activate Jetonomy on each subsite individually. Both approaches work correctly.

Uninstalling

If you deactivate Jetonomy, your data is preserved. Only a full uninstall (delete) removes the wp_jt_* tables, all plugin options, and all registered capabilities, giving you a clean removal with no database debris. Jetonomy Pro behaves the same way: uninstalling it removes every wp_jt_pro_* table, its options and user meta, and its scheduled tasks (fully covered as of 1.5.0).

What's Next?

Run the three-step setup wizard to choose your community URL, create your first space, and go live.

Run the Setup Wizard →

Setup Wizard

After you activate Jetonomy, a three-step wizard walks you through the only decisions you need to make before your community goes live. The whole process takes about two minutes.

Jetonomy setup wizard with step-by-step configuration

What You Will Learn

  • How to set your community URL slug
  • How to choose between creating your first space manually or loading demo data
  • What the wizard does, and what you can always change later

Opening the Wizard

Click the blue notice at the top of your WordPress dashboard, or go to Jetonomy → Dashboard and click Launch Setup Wizard.

The wizard runs in a full-screen overlay. You can close it at any time. Your progress is saved, and you can return to finish it later.

Step 1: Community URL

Setup wizard Step 1: the community URL slug field with a default-space-type selector below it

Choose the slug where your community will live on your site.

The default is community, which gives you yoursite.com/community/. You can change this to anything that fits your site: forum, hub, members, discuss, or your brand name.

Example slug Resulting URL
community yoursite.com/community/
forum yoursite.com/forum/
hub yoursite.com/hub/

Default space type: Also on this screen, choose the default type for new spaces you create. Your options are:

  • Forum - open-ended threaded discussion
  • Q&A - questions with votable answers; the best answer can be marked accepted
  • Ideas - feature requests and votes with a status-lane roadmap
  • Feed - short-form cards for status updates, introductions, and sharing work

You can create spaces of any type regardless of what you choose here. This setting just controls the default when you click "Add Space" later.

Tip: You can change your community URL slug later in Jetonomy → Settings → General. Jetonomy automatically flushes permalink rules when you save.

Make the community your homepage: Under Jetonomy → Settings → General → Community Setup, enable Show the community home on the site front page to serve the community home at your site root. This takes precedence over the WordPress "Your homepage displays" setting, and all other community URLs, posts, and feeds keep working unchanged.

Step 2: First Space

Setup wizard Step 2: the choice between Path A (create your first space) and Path B (load sample data)

This step gets real content into your community so it is ready to share the moment you finish. Choose the path that fits where you are right now.

Path A: Create Your First Space

Choose this if you are setting up a production site and want to start with your own content.

  1. Enter a name for your first space (e.g., "General Discussion").
  2. The space uses the default type you picked in Step 1 (Forum, Q&A, Ideas, or Feed). You can change a space's type any time under Jetonomy → Spaces → Edit.
  3. Click Create Space.

Your space is created and visible immediately after you finish the wizard.

Path B: Load Sample Data

Choose this if you want to try Jetonomy's features before committing to a structure.

Jetonomy seeds your site with a complete, realistic community:

  • Four categories - Start Here, Product & Engineering, Community, and Help & Support
  • A full set of spaces across all four types - Forum, Q&A, Ideas, and Feed - so you can see how each type behaves (for example, "Feature Requests" is an Ideas space with a roadmap, "Bug Reports" is a Q&A space with accepted answers, and "Announcements" is a Feed space)
  • Demo users with realistic avatars, trust level badges, and posting history
  • Sample posts and replies - enough content to see voting, accepted answers, tags, and notifications working in context

This lets you experience the full community interface as a regular member would see it, without writing any content yourself.

When you are ready to go live, click Remove Demo Data on the Jetonomy dashboard. Every demo post, reply, space, category, and user record is deleted in a single operation. Any real content you added alongside the demo data is preserved.

Note: Demo data is tracked internally via a jetonomy_demo_data record. Removal is precise and does not affect any content you created yourself.

Step 3: Done

The final screen confirms your community is live and gives you two quick links:

  • Visit your community - opens yoursite.com/community/ in a new tab so you can see the frontend immediately.
  • Go to admin dashboard - takes you to Jetonomy → Dashboard where you can manage spaces, moderate content, and configure settings.

Everything you configured in the wizard can be changed later:

Setting Where to change it
Community URL slug Jetonomy → Settings → General
Default space type Jetonomy → Settings → General (Community Setup)
Space name, type, join policy Jetonomy → Spaces → Edit
Email notifications Jetonomy → Settings → Email (Notification Defaults)

What's Next?

Your community is live. Now learn what to do in the first hour: create categories, set up your real spaces, and invite your first members.

Your First Community →

Your First Community

Your community is installed and the wizard is complete. This guide walks you through what to do next, from organizing your spaces to inviting your first members, so your community is genuinely ready for people on day one.

Community home page showing spaces organized by category

What You Will Learn

  • How to organize spaces with categories
  • How to create your first real space and choose the right type
  • How to invite members with a shareable link
  • How to customize the look and feel
  • How to import from bbPress or wpForo if you are migrating
  • What the community frontend looks like for your members

Create Categories to Organize Your Spaces

Categories are the top-level groupings in your community. Every space belongs to a category. Before you create more spaces, take a moment to plan your category structure - it is much easier to do now than to reorganize later.

To create a category:

  1. Go to Jetonomy → Categories.
  2. Enter a name and optional description in the form on the left.
  3. Click Add Category.

Your category appears in the table on the right. Drag rows to reorder them. The order here is the order your members see on the community home page.

Jetonomy Categories admin: the add-category form on the left and the drag-to-reorder category table on the right

Tip: Start with two to four broad categories. You can always add more later. Common patterns: "Support / General / Announcements" for a product community, or "Ideas / Questions / Showcase" for a creator community.

Create Your First Real Space

A space is where discussions happen. Each space has a type that shapes how members interact with content.

Choosing a Space Type

Type Best for Key feature
Forum General discussion, announcements, support Threaded replies, newest/popular sort
Q&A Technical help, knowledge bases Votable answers, accepted answer highlight
Ideas Feature requests, roadmaps Status lanes (Planned, In Progress, Shipped, Declined) with roadmap view
Feed Status updates, introductions, sharing work Card feed with optional title and votes

To create a space, you can use either the wp-admin form or the front-end Create Space page. Both produce the same result.

From wp-admin:

  1. Go to Jetonomy → Spaces → Add Space.
  2. Enter a name and optional description.
  3. Pick an icon from the visual Lucide picker (16 defaults plus a search field).
  4. Choose the space type.
  5. Set visibility: Public (anyone can see it), Private (members only see content), or Hidden (not listed, invite only).
  6. Set the join policy: Open, Request to join, or Invite only.
  7. Click Save Space.

The Add Space form showing the Lucide icon picker and the visibility and join-policy selectors

From the front end (so non-admin owners can create spaces too): visit /community/new-space/ while signed in. The form is identical and is available to any role you've enabled under Jetonomy → Settings → General, in the Front-end space creation field.

Your space is immediately available on the community frontend under its category.

Invite Members

You do not need to wait for members to discover your community organically. Jetonomy gives you a direct invite link you can share anywhere.

  1. Go to Jetonomy → Spaces and click on your space.
  2. Click Members in the space navigation.
  3. Click Generate Invite Link.
  4. Set an expiry date, or leave it open with no expiry.
  5. Copy the link and share it via email, Slack, social media, or anywhere else.

The Generate Invite Link panel showing the expiry option and the copyable invite URL

When someone visits the link, they are added to the space immediately after logging in or creating a WordPress account.

Note: Invite links work for any WordPress user registration flow you have configured. If you allow open registration, new members can sign up and join in one step.

Existing WordPress Users

Anyone who already has an account on your WordPress site can visit yoursite.com/community/ and join public spaces by clicking Join Space. Their existing avatar, display name, and email are used automatically.

Customize the Appearance

Jetonomy inherits your theme's fonts, colors, and border radius automatically via WordPress theme tokens. If you are using BuddyX, this integration is immediate. Jetonomy reads BuddyX's design tokens and matches your brand without any manual work.

To adjust further:

  • Go to Jetonomy → Settings and open the General and Advanced tabs.
  • To override specific templates, create a jetonomy/ folder inside your active theme directory and drop in any template file from wp-content/plugins/jetonomy/templates/. Jetonomy always checks your theme folder first.

Tip: You do not need to copy all templates. Only override the ones you want to change. Unmodified templates are served directly from the plugin.

Importing from bbPress, wpForo, or Asgaros

If you are migrating an existing community, Jetonomy includes a built-in importer for three sources.

The Jetonomy Import screen with the source selector and a detected-data summary such as "Found: 12 forums, 3,847 topics, 28,419 replies"

  1. Go to Jetonomy → Import.
  2. Select your source plugin: bbPress, wpForo, or Asgaros Forum.
  3. Jetonomy auto-detects your existing data and shows a summary, for example: "Found: 12 forums, 3,847 topics, 28,419 replies."
  4. Run a Dry Run first to check for any mapping issues.
  5. Click Start Import when you are ready.

Imports run in background batches. You can close your browser and come back. The import continues via WP-Cron and resumes from where it left off if interrupted.

What gets migrated:

Source Jetonomy
Forums Categories + Spaces
Topics Posts
Replies Replies
Users WordPress users + Jetonomy profiles
Inline images and attachments (1.8.0+) Media library + Jetonomy attachments

The Community Frontend: A Quick Tour

Once you have content, here is what your members will see.

Community Home (/community/)

The home page lists all categories with their spaces. Each space card shows the post count, member count, and a recent activity indicator. Members can sort by activity or browse by category.

Space Listing (/community/s/space-slug/)

Inside a space, members see a topic list with vote scores, reply counts, author avatars, tags, and time. They can filter by Latest, Popular, or Unanswered. A New Post button appears in the top right for members who have permission to post.

Single Topic (/community/s/space-slug/t/topic-slug/)

The topic view shows the full post, vote buttons, and all replies. Replies are threaded up to three levels deep. For busy topics with many replies, Jetonomy loads the first 10 and last 10 replies by default, with a gap-loader button in between to fetch more. This keeps page load fast regardless of reply count.

In Q&A spaces, the accepted answer is pinned to the top and highlighted with a green checkmark.

The community sidebar (where the theme layout places it) shows active members, trending tags, and recent activity. The exact sidebar layout depends on your theme.

Note: All community pages are server-side rendered. There are no JavaScript-only pages, so every URL is indexable by search engines out of the box.

What's Next?

Now that your community is live and populated, learn how to organize it further with spaces and categories, including visibility rules and per-space permissions.

Spaces and Categories →

In-Page Authentication

Jetonomy can handle Login, Register, and Forgot Password right inside your community pages, with forms that match your theme instead of bouncing members to the generic WordPress login screen. It does this through its own /auth/* REST endpoints, so signing in to upvote a post or reply to a thread happens in place, without a jarring redirect to wp-login.php and back. This is an optional enhancement you can set up at any time after launch.

What You Will Learn

  • Where the in-page auth forms appear and how they behave
  • Why the Login block improves member experience over a bare login screen
  • How captcha protection now covers signup, not just posting
  • How signed-out interactions and Private community mode route visitors
  • How to customize the auth surface with theme tokens

Where the Forms Appear

The Jetonomy Login block rendered inline on a page, showing the Login, Register, and Lost Password tabs

In-page auth is delivered by the Login block (wp:jetonomy/login). Place the block on any page or template, and it renders Login, Register, and Lost Password tabs inline, styled to match your community. The forms submit to Jetonomy's own /auth/* REST endpoints, so a visitor signs in or registers without ever leaving the page the block is on.

Use it wherever a sign-in surface makes sense:

  • A dedicated member-login page you build with the block
  • A sidebar or footer widget area
  • A landing page that gates content behind membership

Signed-Out Interactions

When a signed-out visitor tries to do something that requires an account - upvoting a post, starting a reply, following a space, subscribing to a tag, or bookmarking - Jetonomy sends them to the WordPress login screen (wp_login_url) with a redirect_to parameter back to the page they were on. After they sign in, they land back where they started.

For a fully in-page experience, point those visitors at a page that hosts the Login block instead of relying on the default wp-login.php redirect.

Why This Matters

The old wp-login.php flow worked, but it had three real problems:

  1. Visual jarring. The WordPress login screen does not look like your community. Visitors went from your themed pages to a generic blue-and-white form and back. The break in visual continuity made the community feel less polished.
  2. Lost context. Visitors who clicked "Reply" had to sign in, then find their way back to the thread. WordPress's redirect handling did not always land them on the right page, especially with theme-specific URLs.
  3. Slow perceived load. Two full page navigations for what should be a quick "sign me in and let me reply" step.

The Login block fixes all three. The forms render inline on the page you place the block on, look like the rest of the community, and submit through the /auth/* endpoints without a full page navigation.

Captcha Now Protects Signup

Site owners can configure reCAPTCHA or Cloudflare Turnstile keys under Jetonomy → Settings → Anti-spam. Before 1.4.0, those keys only protected post and reply submission. Bots could still register accounts freely.

From 1.4.0, the same captcha keys also protect:

  • New account registration
  • Password reset requests
  • Login attempts after repeated failures from the same IP

Nothing needs to change in your settings. If you already had captcha configured, it now extends to signup automatically. If you don't have captcha configured, the auth forms still work; they just have less spam protection.

Captcha Providers Supported

Provider Where to get keys
Google reCAPTCHA v3 https://www.google.com/recaptcha/admin
Cloudflare Turnstile https://dash.cloudflare.com/?to=/:account/turnstile

Choose whichever fits your stack. Turnstile is recommended if you're privacy-conscious or already on Cloudflare; it runs without challenging visitors in most cases.

Who Still Uses wp-login.php

The Login block gives members an in-page sign-in surface. The standard WordPress site-wide login is unchanged.

Administrators (anyone with the manage_options capability) sign in at wp-login.php and reach wp-admin/. That's intentional. Site owners need a reliable way to get into the admin area even if community pages have an issue, and security plugins, two-factor plugins, and SSO integrations all hook into wp-login.php.

In practice:

  • Members can use a page hosting the Login block, or fall back to wp-login.php
  • Signed-out interactions that require an account redirect to wp-login.php unless you route them to a Login-block page
  • Any plugin you have that customises wp-login.php (login restrictions, two-factor, branding) still works for admin login

Private Community Mode

If you've set your community to Private (the Public / Private access control under Jetonomy → Settings → General → Access Control), signed-out visitors cannot read any community content.

Every community URL redirects an anonymous visitor to the WordPress login screen (wp_login_url) with a redirect_to parameter back to the page they were trying to reach, so they land there as soon as they sign in. If you've built a page with the Login block, point your members at it for an in-page sign-in instead.

Registration can be disabled separately if you only want to allow invited members. In that case the Login block hides its Register tab.

Customization

The auth surface uses the same --jt-* design tokens as the rest of Jetonomy. That means your theme's brand color, fonts, border radius, and spacing are picked up automatically. No custom CSS required for a polished match.

Light Auth Surface in Dark Mode

The Login block staying light while the surrounding community page is in dark mode

There's one intentional exception: the Login block stays in light mode even when the rest of your community is in dark mode. This is a deliberate UX choice. Sign-in forms in dark mode are statistically harder to read and easier to mistype, especially on mobile. Keeping the auth surface light maintains form readability where it matters most: at the point of conversion.

If you want to override this and run a dark auth surface (for a fully dark community theme), you can do it via CSS:

.jt-login-block {
  --jt-bg: #1a1a1a;
  --jt-text: #f5f5f5;
}

Customising Form Labels

All auth form labels are translatable through the standard WordPress translation pipeline. They use the jetonomy text domain. If you're running a translated site, the forms pick up your translations on the next load.

What's Next?

With onboarding complete, fine-tune your community in the settings screens, starting with General settings for your community URL and access controls.

General Settings →

Admin Dashboard

The Jetonomy admin dashboard is your first stop after logging in to wp-admin. It shows a live snapshot of community health and gives you one-click access to the most common management tasks.

The Jetonomy admin dashboard: six stat cards across the top, the Recent Activity feed on the left, and the Quick Actions, Demo Data, and System Info panels in the right sidebar

What You Will Learn

  • What each stat card measures
  • How to use the Quick Actions panel
  • What the Recent Activity feed shows
  • Where to find System Info

Go to Jetonomy → Dashboard to access this screen. It is also the landing page when you click Jetonomy in the wp-admin sidebar.

Setup Wizard Notice

On a fresh install where the setup wizard has not been completed, a blue notice appears at the top of the dashboard asking you to run the wizard. The wizard creates your first space and seeds optional demo content. Once you complete it, the notice disappears and does not return.

Stat Cards

Six cards show current counts for the most important community metrics. Counts update in real time - refreshing the page fetches the latest values.

Card What It Counts
Total Posts All published posts across every space
Total Replies All published replies across every post
Active Spaces Spaces with status "active"
Registered Users Total WordPress users with at least one Jetonomy profile row
Pending Flags Flags in the moderation queue that have not been resolved
Posts Today Posts created in the current calendar day (UTC)

When the Pending Flags count is greater than zero, the card turns orange and shows a Review flags → link that takes you straight to the moderation queue - so the count is a one-click action, not just a visual signal. On the moderation queue itself, the "N pending" badge updates live as you resolve each flag, without a page reload.

Recent Activity Feed

The Recent Activity table on the left side of the dashboard shows the most recent member actions logged in the activity system. Columns:

Column Description
User Display name of the member who took the action
Action What they did (colored dot indicates category: green = create, blue = vote, orange = moderation)
Object The post, reply, space, or user the action involved, with a link to the relevant admin page
When Relative time (e.g. "3 minutes ago")

If no activity has been logged yet, a placeholder message appears. The feed is read-only - use it for monitoring, not for taking action on specific items.

Quick Actions Panel

The right sidebar has a Quick Actions card with three buttons:

Button What It Does
Create Space Opens the new-space form at Jetonomy → Spaces → Add New
View Community Opens the community home in a new browser tab
Flush Rules Triggers flush_rewrite_rules() to rebuild WordPress permalink rules

Use Flush Rules any time your community URLs return 404s after changing the base slug, activating a new plugin, or running a migration.

Demo Data Notice

If you used the setup wizard's demo-data option, a yellow card labeled Demo Data Active appears below Quick Actions. Click Remove All Demo Data to delete all demo posts, replies, spaces, and the related setup option.

This card only appears while jetonomy_demo_data option is set. It disappears permanently once you click Remove.

System Info

A small table at the bottom of the sidebar shows:

Row Value
Plugin Version Current Jetonomy version constant
DB Version The database schema version currently applied
PHP Version Server PHP version
WordPress Version WordPress core version
Base URL The configured community base slug (e.g. /community/)

Use this table when filing a support request or diagnosing compatibility issues. Copy the values rather than describing them from memory.

Pro Analytics Widget (Jetonomy Pro)

When Jetonomy Pro is active and the Analytics extension is enabled, Pro adds an analytics mini-widget to the sidebar showing recent engagement trends. A full analytics dashboard is available at Jetonomy → Analytics.

When Pro is not active, a small card in the sidebar describes what the Analytics extension adds and links to the Jetonomy Pro store page.

What's Next?

Give members a sign-in experience that matches your community by adding in-page login and registration.

In-Page Authentication →

Instant Navigation

Moving around a Jetonomy community does not reload the page. Clicking from the home feed to a space, from a space to a member profile, or from search results to the leaderboard swaps the view in place: the surrounding layout stays put, the address bar updates to the new URL, and the browser back and forward buttons work exactly as expected. Posting a reply and casting a vote update in place too, without any page reload at all.

What You Will See

As a member, instant navigation means the community feels responsive. Switching between areas is immediate - there is no white-flash load between pages, and the persistent navigation bar never disappears and reappears. Scroll position on the sidebar and the header persist across moves.

As a site owner, instant navigation is automatic. There is nothing to configure. It activates as soon as Jetonomy loads on the community page and works across all the views Jetonomy renders.

Which Views Navigate in Place

The following views load via the in-place router:

  • Home feed - the community landing page with category and space listing
  • Category pages - a filtered view of spaces within a category
  • Space listing pages - the topic list for any space
  • Space members - the member roster for a space
  • Tag pages - topics grouped by a community tag
  • User profiles - any member's public profile page
  • Search results - the community search view
  • Leaderboard - the top contributors ranking
  • Notifications (Pro) - the notification inbox
  • Messages and conversations (Pro) - the direct messaging views

Two views always load with a full page navigation because they use rich editor scripts that require a clean initialization:

  • Single topic with replies - the reply composer and code syntax highlighter boot on page load and do not re-initialize after an in-place swap
  • New topic form - the topic composer has the same requirement

Every other link in the community navigates in place. External links, new-tab links, and links outside the community URL space are passed to the browser unchanged.

What Happens When JavaScript Is Off

Instant navigation is a progressive enhancement. Every link in the community is a plain HTML anchor. If JavaScript is disabled or the router encounters an error on a particular navigation, the browser performs a normal full-page load to the destination. Members reach the correct page either way.

How It Works

Instant navigation is built on the WordPress Interactivity API client-side router, the same routing engine used in the Block Editor. When a member clicks an internal community link, Jetonomy's global store intercepts the click, fetches the new view's HTML from the server, and swaps the main content region in place. The address bar is updated via the browser's History API, and focus is moved to the new content region for keyboard and screen-reader users.

After each navigation, Jetonomy fires a jetonomy:navigated custom DOM event so that other scripts - such as the load-more pagination and the icon picker - can re-initialize on the swapped content without needing a full page load.

Voting, replying, and other in-place mutations work through the Interactivity API store's reactive state rather than the router, so they update the visible counts and UI immediately without any navigation at all.

Welcome Banner

A first-time visitor who lands on your community home page sees a welcome banner before anything else. It introduces the community with a short heading and a sentence of context, shows live activity numbers, and gives them two clear choices: create a free account or log in. Logged-in members never see it.

What the Banner Contains

The banner has three parts:

Heading - A short introductory line. The default reads "Welcome to [community title]", where the title comes from your community name in settings.

Subheading - One sentence of context. If you have set a community tagline under Jetonomy → Settings → General, it appears here. If you have not set a tagline, the default reads: "Ask questions, share what you build, and join the discussion. Create a free account to post, vote, and follow the spaces you care about."

Community pulse - Three live numbers drawn from your community data: total member count, total post count, and posts published this week. The "this week" figure only appears when it is greater than zero, so a brand-new community with no recent activity shows only the member and post counts. The pulse numbers are cached for one hour so the query does not run on every page view.

Call to action - Two buttons below the pulse. "Create free account" links to the WordPress registration page. "Log in" links to the login page with a redirect back to the community home after sign-in.

Who Sees the Banner

Only signed-out visitors see the banner. Logged-in members go straight to the space and category listing without an intro screen. If registration is disabled on your site, the "Create free account" button links to a WordPress registration page that will itself tell visitors registration is closed - the banner does not check this itself.

Customise

The heading and subheading are both filterable. Add the following to your theme's functions.php or a site-specific plugin to override them:

// Override the welcome heading.
add_filter( 'jetonomy_home_welcome_heading', function( $heading ) {
    return 'Join the conversation';
} );

// Override the welcome subheading.
add_filter( 'jetonomy_home_welcome_subheading', function( $sub ) {
    return 'Ask questions, share ideas, and connect with others.';
} );

Both filters receive the current string as their only argument and expect a plain-text string in return. HTML is escaped on output, so do not pass markup.

The banner's visual style follows the same --jt-* design tokens as the rest of the community, so it automatically picks up your theme's brand color, typography, and border radius with no extra CSS.

Anonymous Posting

Anonymous Posting lets members start topics and write replies without showing their name or avatar to other members. It is a Pro feature, and it is off until you turn it on - both site-wide and per space.

How it works at a glance

  • A member ticks Post anonymously (new topic) or the Reply anonymously toggle (reply) when writing.
  • Everyone else sees the author as "Anonymous" with a neutral silhouette - in the feed, on the topic, in replies, in search, in the RSS feed, in notifications, on profiles, and in the mobile app.
  • The member's real identity is still stored privately, so moderation, rate limits, and reputation keep working - and a site administrator can reveal the real author when needed. Every reveal is written to the activity log.

Turn it on

Anonymous posting needs two switches, both on:

1. Enable the feature (site-wide)

Go to Jetonomy ▸ Extensions and switch Anonymous Posting on. That extension toggle is the global master switch - there is no separate settings screen for it.

2. Allow it in a space

Anonymous posting only appears in spaces where a space admin has opted in:

  1. Open the space and go to Edit ▸ Anonymous.
  2. Tick Allow anonymous posts and save.

Turn it on only where it fits - for example a support, feedback, or sensitive-topics space. Leave it off everywhere else. A space where it is not allowed never shows the anonymous option, even while the extension is enabled.

What a member sees

In a space that allows it:

  • New topic: a Post anonymously checkbox in the composer.
  • Reply: an anonymity toggle (mask icon) in the reply toolbar - it highlights when active.

When they submit with it on, their post shows as Anonymous to everyone else. They can still edit or delete their own anonymous post, and they still receive replies and reactions to it.

Revealing the real author (administrators only)

Only site administrators can reveal who is behind an anonymous post or reply - space moderators cannot. On a post or reply, an admin uses Reveal author. The real name is shown only for that explicit action, and every reveal is recorded in the activity log (who revealed it, which item, and when), so reveals stay accountable.

Use this sparingly - for abuse investigations, not routine browsing. Anonymous stays anonymous by default, even for administrators, until they explicitly reveal.

Good to know

  • The real author is never discarded. Anonymity hides identity from other members on every surface; it does not make the post authorless. That is what lets you moderate, rate-limit, and - if necessary - reveal.
  • Both switches are required. Disabling the extension, or turning the space option off, immediately stops new anonymous posts. Posts that were already anonymous stay masked.
  • Abuse: because the poster is still a real, rate-limited member behind the scenes, blocking and standard moderation remain the remedy for misuse.

File Attachments

File Attachments lets members attach files - images, PDFs and documents - to topics and replies. As of 1.8.0, viewing attachments works in the free plugin. Jetonomy Pro adds the ability to attach new files, plus a lightbox, an inline PDF viewer, and configurable size/type limits.

What members see without Pro

Any attachment already on a post or reply - from a forum import, the mobile app, or the REST API - renders even on a free-only install:

  • Images show an inline thumbnail.
  • Everything else (PDFs and documents) shows a download chip with the file name, size, and type.

If you deactivate Pro, your files keep showing. Attachment storage and display live in the free plugin as of 1.8.0, so nothing disappears - you just lose the composer, the lightbox, the inline PDF viewer, and the size/type limits until Pro is active again.

What Pro adds

With the File Attachments extension enabled, the composer gains an Attach files control (a full "Attach files" button on the new-topic form, a compact paperclip in the reply toolbar) so members can attach new files. The display also gets richer:

  • Images get a lightbox (click to enlarge).
  • PDFs get a first-page preview with filename · pages · size and an Open viewer button that opens the PDF inline (page navigation + zoom, or open in a new tab).
  • Documents (DOCX, XLSX, PPTX, ODT, TXT, CSV) keep the same download chip you get without Pro.

Turn it on

Go to Jetonomy → Extensions and switch File Attachments on.

Configure it

Under Jetonomy → Settings → Attachments you control:

  • Allowed file types - tick the image and document types members may attach. SVG is never available (it can carry scripts).
  • Max file size - per file, in MB (default 10).
  • Max files per post/reply - default 5.

Every upload is validated against this allow-list on the server (file extension and actual content must match an allowed type), so the settings are a real security boundary, not just UI.

Good to know

  • PDF previews need server support. The first-page thumbnail is generated by your host's image tools (Imagick + Ghostscript). If the host can't rasterise PDFs, the PDF card falls back to a document icon - the file still opens and downloads normally.
  • The viewer is only loaded when needed. The PDF viewer library is downloaded the first time a member opens a PDF, never on a normal page load, so it doesn't slow the community down.
  • Removing an attachment detaches it from the post/reply; genuinely orphaned uploads are cleaned up automatically by a daily background task.
  • Deleting a topic or reply removes its attachment links.

Spaces & Categories

Organize your community into discussion spaces.

Categories are the top-level groupings members see on your community home page - think of them as the tabs or sections that organize your spaces. Every space belongs to a category, so it makes sense to set up your categories first, then create spaces inside them.

Jetonomy Categories admin screen with the Add New Category form on the left and the category table on the right

What You Will Learn

  • How categories relate to spaces (and why you set them up first)
  • How to create, edit, delete, and reorder categories
  • How category visibility works
  • How icons and colors appear in navigation

Categories vs Spaces

  • A category is a label that groups related spaces together (for example "Support", "Community", or "Product").
  • A space is the actual discussion area where members post topics and replies.

A space must be assigned to a category to appear in the community navigation. So the normal setup order is: create a category, then create one or more spaces inside it. See Creating Spaces once your categories are ready.

Where to Find It

Go to Jetonomy → Categories in your WordPress admin.

This screen is administrator-only by default - it requires the jetonomy_manage_settings capability, which is granted only to administrators. Editors and other roles do not see this page.

Page Layout

The Categories screen is split into two panels side by side:

  • Left - Add New Category form for creating a new category
  • Right - Categories table listing existing categories and their children

Creating a Category

Fill in the Add New Category form and click Add Category.

Field Required What it does
Name Yes Shown in navigation and on the category page
Slug No Auto-generated from the name if left blank. Used in the URL.
Description No Optional text shown on the category listing page
Parent Category No Nest this category under an existing one. Two levels maximum.
Visibility No Controls who can see this category (see below)
Icon No Choose from the built-in Lucide icon picker
Color No A color swatch shown next to the category name in navigation

Visibility Options

Option Who can see the category
Public All visitors, including logged-out visitors, when guest access is on
Private Logged-in members only
Hidden Not shown in navigation or listings; the direct URL still works

A category's visibility does not override the visibility of the individual spaces inside it. A public category can still contain private spaces - each space keeps its own visibility setting.

Editing a Category

Click Edit in the row actions under any category name. An Edit Category dialog opens with the same fields as the creation form. Make your changes and click Update Category.

Deleting a Category

Click Delete in the row actions. A confirmation prompt appears before the delete runs.

Warning: Deleting a category does not delete the spaces inside it. Those spaces lose their category assignment and become "uncategorized," which means they drop out of the community navigation until you reassign them. There is no undo - reassign the spaces first if you want to keep them visible.

Reordering Categories

Drag the handle icon at the far left of any row to reorder categories. The new order saves automatically when you drop the row. Child categories follow their parent when the parent moves.

Child Categories

Set Parent Category when creating or editing a category to nest it under an existing top-level category. The table shows children indented below their parent. Two nesting levels are supported.

Use the search box in the table toolbar to filter categories by name. Clear the search to see all categories again. Searching does not affect the Add New Category form.

Rows Per Page

The dropdown in the table toolbar controls how many categories appear per page (20, 50, or 100). The list refreshes as soon as you change the selection - useful once you have a large number of categories.

What's Next?

Now that your categories are set up, create the spaces members will actually post in.

Creating Spaces →

Spaces are the discussion areas inside your community - each one has its own topic listing, member list, and settings. This guide shows you how to create, configure, and manage them.

Admin space editor with name, description, and settings fields

What You Will Learn

  • What spaces are and how they relate to categories
  • Every field on the space creation form
  • How the space header looks on the frontend
  • How to edit and archive an existing space

What Is a Space?

A space is a focused discussion area organized around a single topic or purpose. Examples: a "General Discussion" space, a "Product Feedback" space, a "Help & Support" space.

Every space belongs to a category. Categories are the top-level groupings (like tabs or sections) that members see on the community home page. A space without a category will not appear in the community navigation, so set your categories up first. See Categories for how to create and organize them.

Tip: Plan your space structure before creating anything. Too many spaces fragment your community early. Start with three to five and add more as demand grows.

Creating a Space

You have two ways to create a space, depending on who needs to do it:

  • wp-admin path (admins): Go to Jetonomy → Spaces in your WordPress dashboard and click Add New Space.
  • Front-end path (anyone you choose): Visit /community/new-space/ while signed in. This page is available to any user role you've allowed under Settings → Front-end space creation, so you can let space owners or moderators create spaces without giving them wp-admin access. See Creating Spaces From the Front End for the full walkthrough.

Both paths show the same fields and produce the same result. The rest of this section describes each field.

Basic Information

Title - The name members see in listings, the space header, and breadcrumbs. Keep it short and descriptive.

Slug - The URL-safe identifier for this space. Jetonomy auto-generates a slug from the title. The final URL will be /community/s/your-slug/. You can change the slug, but doing so after posts exist will break any existing links.

Description - A short sentence or two explaining what this space is for. This appears in the space header below the title and in category listing cards. It also populates the meta description for search engines.

Icon - The space icon is selected from Jetonomy's built-in Lucide icon picker (16 default icons plus 8 extras revealed by the "Show more" button, plus a search field to find any other Lucide name). The chosen icon appears in the space header and on category listing cards alongside the title. Icons render as crisp SVGs at every size, so they read clearly on mobile.

Category - Select which category this space belongs to. A space must be assigned to a category to appear on the community home page.

Space Configuration

Type - Choose Forum, Q&A, or Ideas. This controls how posts and replies behave. See Space Types for a full explanation of each.

Visibility - Controls who can see the space and its content. Options: Public, Private, or Hidden. See Membership & Join Policies for details.

Join Policy - Controls how members gain access. Options: Open, Approval Required, or Invite Only.

Defaults: if you leave Visibility and Join Policy untouched, a new space is Public + Open - anyone can find, read, and join it. Change them for private or invite-only spaces. Note that Hidden visibility always forces Invite Only; see Membership & Join Policies.

Click Save Space to publish it immediately.

The Space Header (Frontend)

Every space has a header at the top of /community/s/your-slug/ showing:

  • The Lucide icon at large size
  • The space title and description
  • A stat bar with total post count, member count, and last activity time
  • A Follow button for logged-in users (subscribes them to new post notifications)
  • A Join button when the space requires membership

Members who have already joined see the Join button replaced with their role badge (Member, Moderator, or Admin).

Editing a Space

Two paths, same fields:

  • wp-admin: Go to Jetonomy → Spaces, find the space in the list, and click Edit.
  • Front end: Visit the space at /community/s/your-slug/ while signed in as a space owner / moderator and click the Edit space button in the space header. See Editing Spaces From the Front End for screenshots.

All fields are editable from either path, including the type and visibility settings.

Note: Changing the space type after content exists does not reformat old posts. Existing posts keep their original structure. Only new posts use the new type's behavior.

Archiving a Space

To archive a space, open it for editing and set its Status to Archived. An archived space becomes read-only - members can read existing posts and replies, but cannot create new ones. The space remains visible in listings with a clear "Archived" label.

Archived spaces do not count toward activity stats on the community home page.

To permanently remove a space, click Delete in the space list. This action also deletes all posts, replies, votes, and member records inside that space. It cannot be undone.

What's Next?

Learn how each space type changes the way posts and replies behave.

Space Types →

The space type you choose determines how posts are structured, how replies work, and what extra features appear. Pick the right type and your community will feel purpose-built - pick the wrong one and members will feel like they are fighting the interface.

Q&A space showing questions with accepted answers and vote counts

What You Will Learn

  • What each space type is designed for
  • How posts and replies behave differently per type
  • The unique features each type unlocks
  • How to change a space type after creation

The Four Space Types

Forum

Forum is the default type. It is the right choice for general discussion, support, announcements, or any conversation without a single "correct" answer.

How it works:

  • Members post a topic with a title and rich content.
  • Replies thread up to three levels deep (reply to a reply to a reply).
  • Votes on replies surface the best contributions via the Best sort, but no reply is formally "accepted."
  • Topics can be sorted by Newest, Oldest, or Best on the space listing page.

Use Forum for: support channels, general discussion, community announcements, staff Q&A sessions.

Q&A

Q&A is built for questions that have a definitive best answer. It follows the model made popular by Stack Overflow. The person who asked the question marks one reply as the accepted answer.

How it works:

  • Every post is a question. The title should be phrased as a question.
  • Replies are answers. Each answer is voted on independently.
  • The post author sees an Accept button on every reply. Clicking it marks that reply as the accepted answer and pins it to the top of the reply list, regardless of sort order.
  • The accepted answer author earns a reputation bonus (+15 points).
  • The post listing shows an "Answered" badge on topics with an accepted answer. This badge also appears on the space list so members can see at a glance which Q&A spaces have resolved questions.

Use Q&A for: help & support, how-to guides, technical documentation requests, troubleshooting.

Tip: The Unanswered filter on the space listing page shows only topics with no accepted answer. This is a powerful tool for community moderators and support teams tracking open questions.

Ideas

Ideas is built for feature requests, product feedback, and roadmap voting. Each idea has a status that you control, and members vote to indicate demand.

How it works:

  • Members submit ideas with a title and description.
  • Other members upvote (or downvote) to signal interest. Vote score drives the default sort order.
  • Each idea has an Idea Status that the space moderator or admin updates manually:
Status Meaning
Planned On the roadmap
In Progress Being built right now
Shipped Completed and available
Declined Will not be implemented
  • Status updates appear in the reply thread as a system activity entry, so members can see when an idea's status changed.
  • The space listing page has a filter bar showing counts per status, making it easy to browse the roadmap.

Use Ideas for: product feedback boards, feature request trackers, community roadmaps, vote-to-prioritize workflows.

For a full guide to the Ideas roadmap view, see Ideas Roadmap.

Tip: Pick the lightbulb icon from the Lucide icon picker and name the space something like "Ideas & Feedback" to set the right expectation before members click through.

Feed

Feed is designed for short-form status updates - brief posts without a required title. Posts render as feed cards rather than the row list used by Forum and Q&A, so it behaves more like an activity stream than a traditional forum.

How it works:

  • The post title field is optional. Members can post a standalone message, image, or link.
  • Posts appear in a card-style feed sorted chronologically by default.
  • Replies work the same as Forum type, threaded up to three levels deep.
  • Voting is available, but the feed sort does not default to Best - it defaults to Latest.

Use Feed for: member introductions, community announcements, daily check-ins, showcasing work, open-ended community updates.

Empty-State Copy Adapts to the Space Type

A space with no posts yet does not show a generic "No posts" message. The empty state speaks the space's own language so the first visitor knows exactly what to do, and its call-to-action button matches:

Space type Empty-state message Button
Forum "No posts yet. Be the first to start a discussion!" New Post
Q&A "No questions yet. Ask the first one and get answers from the community." Ask a question
Feed "Nothing posted yet. Share the first update." Share an update
Ideas "No ideas yet. Suggest the first one and let the community vote." Suggest an idea

The button only appears for a visitor who is actually allowed to post in the space. A member (or anyone who can join an open space) sees the call-to-action; a visitor who cannot post sees the message without a button.

Note: Space cards on the community home and category listings also carry an "Active [time] ago" recency signal (for example, "Active 2 hours ago"), so members can tell a thriving space from a dormant one at a glance. A space with no recorded activity shows no label.

Changing the Space Type

You can change the type of an existing space at any time. Either open it in Jetonomy → Spaces in wp-admin, or use the Edit space button on the space header itself (front-end edit), and update the Type field.

The change takes effect immediately for all new posts. Existing posts keep their original structure. A Q&A post does not lose its accepted answer, and an Ideas post does not lose its status history.

If you change a Q&A space to Forum, the Accept button disappears from the UI but existing accepted answers remain stored in the database.

What's Next?

Learn how to control who can see your spaces and how members join them.

Membership & Join Policies →

Every space has two controls: who can see it (visibility) and how members get in (join policy). Combining them gives you precise control over every access scenario - from fully public forums to invite-only private communities. The two controls are independent in every combination except one: a Hidden space is always Invite Only (see the note under Hidden below).

Per-space access controls on the space edit screen: the Visibility selector (Public/Private/Hidden), the Join Policy selector (Open/Approval/Invite), and the Who Can Post / Who Can Reply dropdowns

What You Will Learn

  • The three visibility levels and when to use each
  • The three join policies and how they differ
  • Per-space posting restrictions
  • Space member roles and what each can do
  • How to combine visibility and join policy for common use cases

What you get if you leave the defaults alone: a new space is Public visibility with an Open join policy unless you change it (or unless you have changed the community-wide default under Jetonomy → Settings). That means anyone can find, read, and join the space with no approval - the most open setting. Tighten it from there for private or invite-only communities.

Visibility Levels

Visibility controls whether the space appears in listings and whether non-members can read its content.

Public

The space appears on the community home page, in search results, and in category listings. Any visitor - including users who are not logged in - can read all posts and replies.

Members still need to join (or be approved) before they can post or reply, depending on your join policy.

Use Public when you want maximum reach and SEO value. Most community spaces should start here.

Private

The space itself appears in listings and search results, so any visitor can discover it. Only members can read the posts and replies. Non-members see the space name and description, then a Join or Request to Join button depending on the join policy: Open spaces show "Join" for an instant-access join; Approval Required spaces show "Request to Join" for a moderated admission.

The discoverable part is the space, not its content. A post inside a private or hidden space is not leaked to non-members anywhere: it is excluded from search results, from oEmbed and link-preview cards, from tag pages, from trending and popular lists, and from the recent-posts widgets and home feed. A non-member cannot pull the post body in through any of these public surfaces - only members (and admins) see it.

Private is the right choice when the community should be findable but the content should be gated. It is distinct from Hidden: a Private space is visible in the directory and search; a Hidden space is not listed anywhere.

Use Private for paid membership communities, internal team discussions, or beta program spaces where content should be gated but the space itself should be findable.

Hidden

The space does not appear in any listing, search result, or category navigation. Only members who have already joined - and admins - can see it.

Members can only reach a hidden space via a direct link or an invite link you share with them.

Use Hidden for admin-only spaces, private moderator discussion boards, or early access groups where you control every invitation.

Hidden spaces are always Invite Only. Because a Hidden space has no public Join button, its join policy is locked to Invite Only - members can only enter through an invite link you share. If you pick Hidden in wp-admin, the join policy is switched to Invite Only for you automatically (and if you set a Hidden space back to Open or Approval Required, the visibility drops to Private). From the front-end create/edit form or the REST API, the same combination is rejected on save with the message "Hidden spaces must use the invite-only join policy" - so always pair Hidden with Invite Only.

Note: WP Admins and space admins can always see hidden spaces in the admin panel, regardless of their membership status.

Join Policies

Join policy controls how members gain access to the space. It works in combination with visibility.

Open

Any logged-in user can join instantly by clicking the Join button on the space header. There is no approval step.

Members who join an Open space can post and reply immediately (subject to your per-space posting restrictions).

Use Open for general discussion spaces, community-wide help channels, and any space where you want minimal friction.

Approval Required

When a user clicks Join, they submit a join request. The request goes to the space moderators and admins for review.

Moderators see pending requests in Jetonomy → Moderation → Join Requests. They can approve or decline each request. The user gets a notification when their request is reviewed.

Since 1.5.0, space moderators can also handle requests from the community front-end, without opening wp-admin. On the space Members page (/community/s/:slug/members/), a "Pending join requests" panel lists each requester with Approve and Deny buttons. Approving admits the member instantly and emails the requester that they are in; denying clears the request. This is the same approve/admit path as the wp-admin tab, so a space moderator who has no wp-admin access can still run an Approval Required space end to end.

Approved members can then post and reply immediately.

Use Approval Required when you want to vet members before they can participate - for example, a verified customer support channel or a professional community with admission criteria.

Invite Only

No join button is shown to non-members. Members can only enter via an invite link generated by a space moderator or admin.

To create an invite link, open the space in the admin panel (Jetonomy → Spaces → [space] → Edit) and go to the Invite Links section. Each link has a configurable usage limit and optional expiry date. You can see how many times each link has been used.

Invite links are created in wp-admin. Invite-link management lives on the wp-admin space edit screen, not on the front-end editor. A front-end space owner who does not have wp-admin access can still run an Invite Only space, but a site administrator (or someone with wp-admin access) generates and shares the links for them. The front-end Edit Space page can set the join policy to Invite Only but does not create the links themselves.

Anyone who visits a valid invite link is automatically added as a member - no approval step required.

Use Invite Only for private groups, course cohorts, or closed communities where every member should be explicitly invited.

Per-Space Posting Restrictions

Beyond join policy, each space has two additional settings that control what members can do once inside:

Who Can Post - Controls who can create new topics in this space.

Setting Effect
(Use Global Default) Falls back to the community-wide Who Can Post setting
Members Only All members of the space can post
Moderators & Admins Only space moderators and admins can post
Admins Only Only space admins (and site admins) can post

Who Can Reply - Controls who can reply to existing topics.

Setting Effect
(Use Global Default) Falls back to the community-wide Who Can Reply setting
Members Only All members of the space can reply
Moderators & Admins Only space moderators and admins can reply

These settings let you create announcement-only spaces (post set to Moderators & Admins, reply set to Moderators & Admins), or read-heavy Q&A spaces where only trusted members can contribute.

Space Member Roles

Every member of a space has one of three roles:

Member - Can read, post, and reply within the space's configured restrictions. Can vote, follow, and bookmark topics.

Moderator - All member abilities plus: approve pending posts, pin topics, close topics, move topics, delete posts and replies, manage join requests, create invite links, and review flagged content within the space.

Admin - All moderator abilities plus: change space settings, assign moderator and admin roles to other members, manage access rules, and archive or delete the space.

Note: WordPress site admins can perform all admin-level actions on any space, regardless of their space role.

Promoting members from the front-end (1.3.8+)

Space admins (and site admins) can change a member's space role directly from the front-end members page at /community/s/:slug/members/. Each member row carries a role dropdown; picking a new role saves the change live with inline success or error feedback. There is no wp-admin round-trip to add a moderator and no separate settings screen to open.

The dropdown is hidden for members who cannot manage roles, and a member cannot demote themselves below the level needed to keep at least one space admin. Members visible in the list still have to be members of the space to be assigned a role; the same per-space role rules listed above apply.

Common Visibility + Join Policy Combinations

Goal Visibility Join Policy
Public community forum Public Open
Paid membership forum Private Approval Required
Team-only internal channel Hidden Invite Only
Verified customer support Public Approval Required
Early access beta group Hidden Invite Only
Course community Private Open (link-gated via Invite Only)

What's Next?

See every per-space setting in one place, including how they override global defaults.

Space Settings →

Each space can override the global Jetonomy defaults with its own settings. This page is a complete reference for every per-space option, how it interacts with global settings, and how invite links work.

Admin space editor showing per-space configuration options

What You Will Learn

  • Every per-space setting and what it controls
  • How per-space settings override global defaults
  • How to require moderator approval before posts go live
  • How to create, share, and track invite links

Accessing Space Settings

Go to Jetonomy → Spaces in your WordPress admin, find the space, and click Edit. The settings panel is on the right side of the edit screen.

Per-Space Settings Reference

Posts Per Page

Default: Inherits from global setting (default: 20)

Overrides how many topics appear per page on this space's listing. Set a lower number for low-traffic spaces with long post titles. Set a higher number for high-activity spaces where members scan quickly.

Valid range: 5 to 100.

Require Post Approval

Default: Off

When enabled, every new post submitted by a non-moderator is held in a pending state and does not appear publicly until a moderator approves it.

Moderators and space admins can see pending posts immediately in the space listing with a "Pending" label. They can approve, reject, or mark the post as spam from the topic view or from Jetonomy → Moderation.

The post author receives a notification when their post is approved or rejected.

Tip: Enable this for your early community days when you want to review every submission, then turn it off once you trust your membership base.

Allow Voting

Default: On (inherits from global)

Controls whether upvote and downvote buttons appear on posts and replies in this space. Disabling voting also removes vote scores from the space's topic listing.

In Q&A spaces, voting is always available on replies regardless of this setting - otherwise the Best sort and accepted-answer workflow cannot function correctly.

In Ideas spaces, voting cannot be disabled because it is the core mechanism for prioritizing ideas.

Who Can Post

Default: Anyone (members)

See Membership & Join Policies for the full option set. The per-space value overrides the global default for this space only.

Who Can Reply

Default: Anyone (members)

Controls who can add replies to topics in this space. Overrides the global default for this space only.

Post Prefixes

Default: Off

Prefixes are short, colored labels members can attach to the front of a topic title - for example "Bug", "Idea", "Solved", or "Announcement" - so the listing is easier to scan.

Turn on the Enable prefixes toggle to reveal the prefix list, then add one row per prefix. Each row has a label (up to 50 characters) and a color swatch. Add as many as the space needs, and remove any with the × button.

Once prefixes are enabled:

  • When a member starts a new topic in the space, an optional Prefix dropdown appears on the compose form. They pick one (or leave it blank).
  • The chosen prefix shows as a colored label in front of the topic title on the space listing and the single-topic view, using the color you set.

Prefixes are configured per space, so a "Support" space can offer "Bug / Question / Solved" while an "Announcements" space offers none. You can manage prefixes from either the wp-admin space editor or the front-end Edit Space page.

How Per-Space Settings Override Global Settings

Jetonomy uses a two-layer settings system:

  1. Global settings - Set at Jetonomy → Settings → Community. These are the defaults that apply to every space.
  2. Per-space settings - Set on individual spaces. When a per-space value is configured, it takes precedence over the global value for that space only.

If you leave a per-space setting at "Inherit from global," any future changes to the global setting will automatically apply to that space. If you explicitly set a per-space value, global changes do not affect it.

This means you can configure a sensible default globally and only override the spaces that need different behavior - instead of configuring every space individually.

Access Rules for Membership-Gated Spaces

Access rules restrict a space based on external membership status - not just whether someone has joined the space. They apply to Private and Hidden spaces, where non-members are already blocked from the content and the rule decides who among them gets in.

Go to the Access Rules tab on the space edit screen to add rules.

Adding a restrictive rule to a Public space converts it to Private automatically. A Public space is readable by everyone, so a membership, role, capability, or trust-level rule attached to it would silently do nothing - the content stays open. To stop that "configured but still accessible" trap, Jetonomy switches the space to Private the moment you save such a rule, so the rule can actually gate access. The admin screen tells you it happened. If you did not intend to make the space Private, remove the rule and the space stays Public.

Each rule has three parts:

Rule Type - What to check:

Type What it checks
Everyone Matches every visitor, including logged-out users
Logged In User is authenticated
WordPress Role User has a specific WP role (e.g. Editor)
Capability User has a specific WP capability
Trust Level User's Jetonomy trust level (0 - 5)
Membership User has an active membership. The specific provider - MemberPress or Paid Memberships Pro in free, plus WooCommerce Memberships, Restrict Content Pro, LearnDash, and Learnomy (course or membership plan) in Pro - is chosen within the rule via the matching membership adapter

Access Grant - What to allow:

Grant Effect
Read Can view posts and replies, cannot participate
Participate Can read, post, and reply
Full All participate abilities plus moderator actions

Auto-Assign Role - Optionally assign the member a space role (Member, Moderator, Admin) automatically when the access rule is satisfied. This is useful when you want MemberPress Gold members to automatically become space moderators.

Multiple rules can be stacked. Jetonomy grants the highest matching permission level.

Note: MemberPress and Paid Memberships Pro adapters are available in Jetonomy free. WooCommerce Memberships, Restrict Content Pro, LearnDash, and Learnomy adapters require Jetonomy Pro. The Learnomy adapter gates a space by either a Learnomy course or a Learnomy membership plan.

Invite links let you bring specific people into a space without opening up general membership.

Where to manage invite links: invite links are created and tracked from the wp-admin space edit screen (Jetonomy → Spaces → [space] → Edit → Invite Links), not from the front-end Edit Space page. A space owner without wp-admin access can run an Invite Only space, but a site administrator generates and shares the links on their behalf.

  1. Open the space for editing in wp-admin and go to the Invite Links section.
  2. Click Create Invite Link.
  3. Set an optional Usage Limit (how many people can use this link before it expires).
  4. Set an optional Expiry Date.
  5. Click Generate.

Jetonomy generates a unique URL: /community/invite/abc123def/

Copy the link from the Invite Links table and share it however you prefer - email, Slack, a membership welcome email, etc.

When someone visits the link, they are prompted to log in if they are not already. After logging in, they are automatically added to the space as a Member.

Tracking Usage

The Invite Links table shows each link's current usage count against its limit. Links that have reached their usage limit are automatically deactivated but remain in the table for your records.

You can manually deactivate or delete any invite link at any time.

Space RSS Feeds

New in 1.5.0. Every public space publishes an RSS 2.0 feed of its latest 20 topics at:

https://your-site.com/community/s/{space-slug}/feed/

Feed readers and browsers discover it automatically from the space page. Members and visitors can follow a single space from any RSS reader without creating an account - useful for announcement spaces, changelogs, and "follow this team" workflows.

Privacy is preserved: the feed serves only what a logged-out visitor could already read. Private and hidden spaces return 404 from their feed URL, and switching the whole community to private mode disables all feeds. Developers can adjust feed contents with the jetonomy_space_feed_posts filter.

What's Next?

Learn how to create topics and posts inside your spaces.

Creating Topics →

  • Custom Fields - add structured fields to posts and profiles, configurable per space.

Every Ideas space includes a built-in roadmap view. Instead of scrolling through a flat list of feature requests, visitors can see all ideas organized by status - what is planned, what is being built, what has shipped, and what will not be pursued. This page explains the roadmap, the status lanes, and how status changes are communicated to your community.

What You Will Learn

  • Where to find the roadmap view
  • What the four status lanes mean
  • How admins update an idea's status
  • How status changes surface in notifications and the activity log
  • What the roadmap looks like for community members

What the Roadmap Shows

Ideas roadmap view with four status lanes - Planned, In Progress, Shipped, and Declined - each holding idea cards sorted by vote score

The roadmap is a dedicated view of an Ideas space that groups all ideas by their current status. Access it at:

/community/s/<space-slug>/roadmap/

A link to the roadmap appears in the Ideas space navigation alongside the main topic list. Members do not need to navigate manually - they can switch between the idea list and the roadmap from within the space.

Each status lane shows all ideas in that state, sorted by vote score within the lane (highest votes first). This gives members a clear view of what the community wants most and where each request stands.

Status Lanes

The roadmap has four lanes:

Status What it means
Planned The idea has been accepted and is on the roadmap. Work has not started yet.
In Progress The team is actively building or implementing this idea.
Shipped The idea has been completed and is now available.
Declined The team has decided not to pursue this idea. A reply with context is recommended.

New ideas submitted by members have no status by default. They appear in the main idea list but not in any roadmap lane until a moderator or admin assigns a status.

Tip: When you decline an idea, add a reply explaining why. Members who took the time to submit and vote on an idea deserve a clear answer. Declined ideas with a closing comment are far less likely to be re-submitted repeatedly.

How Admins Update a Status

Any space moderator or admin can change an idea's status:

  1. Open the idea (the single post view).
  2. Find the Status control in the post meta area below the title.
  3. Select a new status from the dropdown: Planned, In Progress, Shipped, or Declined.
  4. Click Update Status.

The status change saves immediately. A system entry appears in the reply thread showing what the status changed from and to, with a timestamp. This gives the idea's full history in one place.

You can also update status from the space admin panel at Jetonomy → Spaces → [Space Name] → Posts. The status column is editable inline from that view, which is useful for processing a batch of ideas at once.

How Status Changes Surface in Notifications

When an idea's status changes, Jetonomy sends notifications across three channels:

Activity log - A system activity entry is created in the idea's reply thread, visible to anyone who opens that idea.

Email digest - If a member follows the Ideas space and has email digest enabled, the status change is included in their next digest email (daily or weekly depending on their preference).

In-app inbox - The idea author receives an in-app notification immediately. All followers of the Ideas space also receive an in-app notification.

Members who do not follow the space will not receive notifications about that specific status change. Encourage members to follow the space after submitting an idea so they stay informed.

Customer-Visible Behaviors

What members see at each stage:

  • New idea with no status assigned - Appears in the idea list. Not shown in any roadmap lane until an admin or moderator picks a status. Vote buttons are active so members can build up signal even before the team triages.
  • Planned - Appears in the Planned lane on the roadmap. A "Planned" badge shows on the idea card.
  • In Progress - Moves to the In Progress lane. Badge updates. Members can see work has started.
  • Shipped - Moves to the Shipped lane. Badge shows "Shipped." Upvote button remains available so members can react positively to the delivery.
  • Declined - Moves to the Declined lane. Badge shows "Declined." Vote controls remain visible.

Ideas can be moved between statuses at any time. Moving a shipped idea back to In Progress (for a revision, for example) is valid and will notify followers again.

What's Next?

The roadmap is a deep-dive on the Ideas space type. To set up a space without leaving the front end, see the front-end create flow.

Create a Space from the Front-End →

Since Jetonomy 1.4.0, members with the right role can create a new space without ever opening wp-admin. The front-end Create Space page lives at /community/new-space/ and gives community owners a way to delegate space creation to trusted regulars, team leads, or paying members without handing out WordPress admin access.

Front-end Create Space form at /community/new-space/ showing the Title, Description, Type, Visibility, Join policy, Category, and Icon fields

What You Will Learn

  • Where the Create Space page lives and who can reach it
  • Which roles are allowed to create spaces, and how to change that
  • Every field available on the form, including the visual icon picker
  • What happens the moment the form is submitted
  • When to use this page versus the wp-admin equivalent

Where The Page Lives

The page is always available at /community/new-space/. It is part of the standard /community/ rewrite group, so it inherits the same theme, header, and footer as the rest of your community pages.

There is no separate menu item by default. Most communities expose the page in two places:

  • A "Create space" button on /community/ for signed-in users with permission
  • A "Start a space" link in the header avatar menu

Both links are conditional. Members who do not have permission never see the link and never see the page either, even if they type the URL directly.

Who Can Create Spaces

Open Jetonomy → Settings → Front-end space creation. The setting is a list of WordPress roles. Tick the roles you want to allow.

The default is Administrator only, which matches the pre-1.4.0 behaviour. Most communities widen this to Editor, Author, or a custom role such as "Community Builder" after they have run for a few weeks and identified trusted members.

A few important notes:

  • The permission is role-based, not per-user. If you want to grant one specific member the ability to create spaces, add them to a role that has it.
  • Granting the right to create a space is not the same as granting the right to moderate every other space. A member who can create one space only moderates the spaces they created, not the whole community.
  • Network admins on multisite have the permission everywhere by default.

The Form Fields

The front-end form covers the fields a member needs to launch a space. The slug is generated automatically from the title, and posts-per-page is set later on the Edit Space page rather than at creation time.

Field What it controls
Title The display name shown in listings and the space header. Required.
Description One or two sentences shown on the space card and the space header.
Type Forum, Q&A, Ideas, or Feed. Cannot be changed after creation.
Visibility Public, Private, or Hidden.
Join policy Open, Approval Required, or Invite Only.
Category Which top-level community category the space belongs to. Optional but recommended for navigation.
Icon A visual icon shown next to the title everywhere the space appears.

The form does its own validation in the browser before submission, then again on the server. Submitting with an empty title returns an inline error rather than a generic failure. The slug is derived from the title automatically and made unique by the server.

The Visual Icon Picker

The icon field is not a free-text field. Jetonomy ships with a Lucide icon picker so every space gets a consistent, professionally-drawn icon.

Lucide icon picker open, showing the 16 default icons, the Show more button, and the search field at the top

The picker shows 16 default icons up front, covering the most common community space themes: users, hand, megaphone, message-circle, help-circle, lightbulb, star, rocket, book-open, award, shield, pin, bookmark, home, hash, and folder.

Click "Show more" to reveal another 8 icons for less common topics. If none of those fit, the search field at the top filters the entire Lucide catalogue by name, so typing "music" surfaces the music note icon, "camera" surfaces the camera icon, and so on.

The picker stores only the icon name, not an SVG, so the icon stays crisp at any size and automatically picks up the active theme's color tokens.

What Happens On Submit

Submitting the form does five things in one transaction:

  1. Creates the space row in wp_jt_spaces
  2. Adds the submitting user as the space admin (role = admin)
  3. Adds the space to the chosen category, if any
  4. Flushes the relevant rewrite caches so the space URL resolves immediately
  5. Redirects the user to the new space at /community/s/<slug>/

There is no approval queue. The space is live the moment the form is submitted. Who may reach the form at all is gated by the jetonomy_create_spaces capability plus the Front-end space creation roles setting, so you control space creation by role rather than by an after-the-fact approval step.

Validation Hints

  • Title is required and cannot be a duplicate of an existing space title within the same category.
  • The slug is generated automatically from the title and made unique by the server. There is no slug field on the create form; you can edit the slug later from the Edit Space page.
  • Description is optional but space cards look better with one.
  • Category, Type, Visibility, and Join policy default to the community-wide defaults set in Jetonomy → Settings.

Permission Gotchas

A few rules that surprise people on first use:

  • Creating is not moderating. A role granted "create spaces" is automatically space admin only for the spaces it creates. It cannot moderate other spaces it did not create.
  • Visibility is per-space, not per-role. A role allowed to create spaces can create a space of any visibility, including Hidden. There is no built-in per-visibility gate; restrict who can create spaces at all via the Front-end space creation roles setting if that matters for your community. One rule does apply to the combination, though: a Hidden space must use the Invite Only join policy. Picking Hidden with Open or Approval Required on the front-end form is rejected on save with "Hidden spaces must use the invite-only join policy" - set the join policy to Invite Only when you choose Hidden. See Membership & Join Policies for the full explanation.
  • Deactivating a member who created a space does not delete the space. The space remains; ownership transfers to the next admin in the space, or to the site administrator if there is no other admin.
  • Slug collisions are checked across the whole site. A member trying to create a space with a slug another space already uses will see an inline error, even if they cannot see the other space.

Front-End Form vs wp-admin

Both paths produce identical spaces. Pick whichever is faster for the situation.

Situation Use front-end Use wp-admin
You're a regular member with permission Yes Not available
You're an admin and already in the community Yes, faster Either
You're an admin setting up the community for the first time Either Either, bulk import easier
You want to create 10+ spaces in one session Either wp-admin has bulk tools
You want to seed a space with demo content wp-admin wp-admin only
You want to change advanced options (access rules, custom roles) wp-admin wp-admin only

The front-end form covers everything a member or space owner needs. The wp-admin editor adds bulk tools and a few advanced toggles that only site administrators ever touch.

Developer Hooks

The two controls that come up most often when customising this page:

  • jetonomy_create_spaces (capability) - granted to roles via the Front-end space creation roles setting. A user without this capability never sees the Create Space link or page. This is the primary gate.
  • jetonomy_use_frontend_space_edit (filter) - returns true to route both the Create and Edit space flows through the front-end UI. Default true. Return false to send these flows to wp-admin instead.

See the Developer Reference for the full signatures and examples.

What's Next?

Once a member has created a space, they often want to tweak it. The Edit Space page lets them adjust the icon, description, join policy, and more without leaving the front-end.

Edit a Space from the Front-End →

Since Jetonomy 1.4.0, space owners and moderators can edit their space directly from the front-end. They click an Edit space button in the space header and adjust the title, description, cover image, icon, type, visibility, join policy, moderation approval, category, posts-per-page, and prefixes without ever loading wp-admin.

Front-end Edit Space page at /community/s/<slug>/edit/ with the Edit space button visible in the space header and the editable fields below

What You Will Learn

  • Where the Edit space button appears and who sees it
  • Every field you can change from the front-end
  • How the cover image upload works without wp-admin permissions
  • The safety rules that prevent a space from being orphaned by accident
  • The difference between archiving and deleting a space
  • When to use the front-end editor versus the wp-admin one

Where The Edit Button Appears

The Edit space button lives in the top right of every space header, just above the post listing. It is conditional: only members with the right role inside that space see it.

The roles that see the button:

  • Space admin - the member who created the space, plus anyone they promote to admin
  • Space moderator - members promoted by a space admin
  • Site administrator - sees the button in every space

Regular members and guests never see the button. If a member loses their moderator role mid-session, the button disappears on the next page load.

The button leads to a dedicated page at /community/s/<slug>/edit/. The URL is permission-checked on every request, so typing it directly without the right role returns a 403.

What You Can Edit

Every space setting that is editable in wp-admin is editable from the front-end. The two editors are kept in lockstep release after release.

Field What it controls
Title The display name shown everywhere the space appears
Slug The URL segment under /community/s/. Changing it sets up a redirect from the old slug
Description The one-line summary shown on the space card and header
Cover image A wide banner image shown above the space header
Icon The Lucide icon shown next to the title
Category Which community category the space belongs to
Type Forum, Q&A, Ideas, or Feed
Visibility Public, Private, or Hidden
Join policy Open, Approval Required, or Invite Only
Require moderator approval for new posts When checked, new posts are held in the moderation queue until a moderator approves them
Posts per page A number from 1 to 100; leave blank to use the site default
Post prefixes Optional tags shown in front of post titles (e.g. "Bug", "Idea")

Changing the type after creation is supported but rarely a good idea. Switching a Q&A space to a Forum keeps every existing post but stops showing the "Mark as answer" affordance. The editor warns you before saving a type change.

The Visual Icon Picker

The icon picker is the same one used on the Create Space page. 16 default Lucide icons up front, 8 more behind a "Show more" button, and a search field that filters the full Lucide catalogue by name.

Changing the icon takes effect immediately on save. Cached pages and listings pick up the new icon on the next refresh. There is no manual cache flush needed.

Cover Image Upload

The cover image field accepts a standard image file. Recommended dimensions are around 1600 x 400 pixels - that is guidance for how the banner is cropped, not an enforced limit.

Uploads go into the standard WordPress media library at /wp-content/uploads/YYYY/MM/, the same place every other site image lives. The space stores only the resulting image URL, so the upload is reusable like any other attachment. The upload endpoint is gated by the logged-in member's edit permission in the space rather than by WordPress's upload_files capability, so a space owner can set a cover without site-wide media upload rights.

There is a Remove cover link below the upload field that clears the image reference from the space. Removing a cover only clears the reference; it does not delete the underlying attachment from the media library.

Role-Protection Rules

The front-end editor is intentionally cautious about anything that could leave a space without an owner.

  • No self-demote. A space admin cannot demote themselves if they are the only admin. The role dropdown skips the "moderator" and "member" options in that case and shows a tooltip explaining why.
  • No last-admin-out. A space admin cannot delete or archive the space if they have set themselves to leave it. The flow forces a transfer-to-another-admin step first.
  • No silent ownership transfer. Promoting another member to admin happens on the Members tab, not on the Edit space page. The editor never moves ownership in the background.

These rules exist because the most common community support ticket pre-1.4.0 was "I accidentally demoted myself and now nobody can edit the space." The front-end editor blocks that path entirely.

Archive vs Delete

The bottom of the Edit space page has two destructive actions, kept visually separate from the save button.

Archive space marks the space read-only. Existing posts and replies stay visible to anyone who could see them before. New posts and replies are disabled. The space disappears from category listings but is still reachable at its original URL. Archive is reversible: re-opening the editor and clicking Unarchive restores everything.

Use archive when:

  • A space has run its course but the content is still worth keeping
  • A seasonal event ends and you want the archive of last year's discussion to remain
  • A topic moves to a different space and you want the old discussions preserved

Delete space removes the space and every post, reply, vote, flag, and subscription in it. It cannot be undone from the UI. The button asks for explicit confirmation (typing the space title) before it fires.

Use delete when:

  • The space was created by mistake and has no real content
  • The content needs to be removed for legal or moderation reasons
  • You are cleaning up after a spam attack

If you are unsure, archive instead of delete. Archiving is reversible; deletion is not.

Front-End Edit vs wp-admin Edit

Both editors produce identical results. They share the same model, the same validation, and the same hooks.

Situation Use front-end Use wp-admin
You're a space owner without admin access Yes Not available
You're a site admin already in the community Yes, faster Either
You want to change advanced access rules Not supported wp-admin only
You want to bulk-edit multiple spaces Not supported wp-admin only
You want to change Pro extensions config Not supported wp-admin only
You want to fix a typo in the description Yes, fastest Either

The front-end editor covers the day-to-day fields a space owner touches. The wp-admin editor adds bulk tools and a handful of advanced settings that only site administrators ever need to change.

What's Next?

If you give space-creation permission to a wider group, expect those members to want a single place to see every space they run or belong to. The My Spaces page does exactly that.

My Spaces →

Discussions & Topics

Create topics, reply, vote, and manage conversations.

A topic is the primary unit of discussion in Jetonomy - every conversation, question, idea, or update starts here. This guide walks through everything that happens from the moment a member clicks "New Post" to when their topic goes live.

New post form with title, content editor, tags, and publish options

What You Will Learn

  • How to open the new post form in a space
  • Every field in the post composer and what it does
  • How Markdown formatting works in the content editor
  • How content moderation and rate limiting affect new members
  • Whether posts publish immediately or wait for approval

Opening the New Post Form

Members can start a new topic in three ways:

  1. Navigate to a space and click the New Post button in the space header.
  2. Visit the direct URL: /community/s/your-space-slug/new/
  3. Click + New from any page in the community - this opens a space picker first, then the form.

The new post form is always scoped to a specific space. If a member arrives via the generic + New button, they choose a space before the form loads. This ensures every topic lands in the right place.

If You Have Not Joined the Space (1.6.0)

Jetonomy checks that you are allowed to post before it loads the composer. If you open the new post page for a space you have not joined, you see a permission prompt up front instead of a blank composer:

  • Open space you can join - a "Join [space]" prompt appears with a button that takes you back to the space to join. Once you are a member, open the new post page again and the composer loads.
  • Restricted space you cannot join - a permission-denied message appears with a link back to the space.

This replaces the old behavior where you could write an entire post and only hit the wall when you tried to submit it. The gate runs first so you never lose a draft to a rejected submission.

The Post Composer

Title

The title field is required for Forum, Q&A, and Ideas spaces. Write a clear, specific title that tells members exactly what the topic is about before they click it.

Good: "How do I set up automatic email digests for my space?" Weak: "Help with emails"

For Q&A spaces, phrase the title as a question - it helps other members find answers when searching.

The title field is optional for Social Feed spaces, where short-form posts without titles are common.

Content

The content field supports rich text via a Markdown toolbar. You do not need to know Markdown syntax - the toolbar buttons handle formatting for you.

Toolbar options:

Button What it does
B Bold text
I Italic text
< > Inline code
Link Insert a hyperlink
Quote Block quote
Image Upload an image from your device

You can also type Markdown directly if you prefer:

  • **bold**bold
  • *italic*italic
  • `code`code
  • > quote → block quote

Images are uploaded to the WordPress media library. Each image is inserted as a standard <img> tag in the content. There is no separate file attachment field - all media goes inline.

Tags

Add up to five tags to help members find your topic through search and tag filtering. Type a tag name and press Enter. Jetonomy auto-suggests existing tags as you type - reusing existing tags is better than creating near-duplicates.

Tags are space-scoped by default. A "bug" tag in your Support space and a "bug" tag in your Dev space are the same tag in the database, but the tag page at /community/tag/bug/ will show posts from all spaces.

Similar Topics Detection

As you type the title, Jetonomy searches for existing topics with similar titles in the current space and shows up to five matches inline below the title field. This is the single best defense against duplicate topics - most duplicates happen because the author simply did not know an existing topic already covered their question.

If you see your question already answered in the list, click the match to jump to the existing topic instead of submitting a duplicate. If none of the matches fit, keep typing - the search re-runs after every few characters.

Similar Topics detection runs a lightweight, debounced search request (about 400ms after you stop typing) against the server search index, so it does not reload the page.

Prefix Selector

If the space has topic prefixes enabled, a Prefix selector appears next to the title field. Pick a prefix (for example, Bug, Suggestion, Solved) from the list and it appears as a colored label in front of your topic in the space listing.

Prefixes are configured per space by the space owner - see the Topic Prefixes guide.

Private Topic Toggle

If your space allows it, a Private toggle appears at the bottom of the composer. Enabling it restricts the topic to you and space moderators only - other space members cannot see it. Use this for sensitive support issues, personal requests, or anything that should stay between you and the moderators.

Not every space allows private topics. If you do not see the toggle, the space owner has disabled the feature.

See Private Topics and Topic Prefixes for the full guide.

Post Type Is Derived Automatically

You do not select the post type - Jetonomy determines it from the space type:

Space type Post type
Forum Discussion
Q&A Question
Ideas Idea
Social Feed Update

The composer adapts its UI accordingly. Q&A spaces show a hint to "phrase as a question." Ideas spaces show the Idea Status selector (visible to moderators). Social Feed spaces make the title optional and show a shorter, Twitter-style composer.

Content Moderation Checks

Before a post is saved, Jetonomy runs three checks:

Rate limiting - New members (Trust Level 0) can submit a maximum of 3 posts per day and 10 replies per day. If a member has hit their limit, the submit button shows an error and the post is not saved. Rate limits reset at midnight UTC. Members at Trust Level 1 and above have higher or no limits, depending on your global settings.

Require post approval - If the space has "Require Post Approval" enabled, the post is saved with a Pending status. It is not visible to other members until a moderator approves it. The submitter sees a confirmation: "Your post has been submitted and is awaiting approval."

Auto-moderation rules (Jetonomy Pro) - If Pro auto-moderation rules are configured, Jetonomy checks the content against those rules before saving. Depending on the rule configuration, the post may be flagged, held, blocked, or marked as spam automatically.

Note: WP Admins and space moderators bypass rate limiting and approval requirements. Their posts always publish immediately.

After Publishing

When a topic publishes successfully:

  • It appears at the top of the space listing under the Latest sort.
  • The author is automatically subscribed to the topic and will receive notifications for new replies.
  • The space's post count increments immediately.
  • Search indexes are updated on the next cron run (typically within a few minutes).

If the topic is pending approval, it does not appear in the listing, does not increment the post count, and the author's subscription is created but held until approval.

What's Next?

Learn how replies work, how threading is structured, and how to accept answers in Q&A spaces.

Replies & Threading →

  • Embedding the Composer - drop a "start a topic" box onto any WordPress page, post, or landing page using the Compose Topic block or the [jetonomy_compose_topic] shortcode.
  • You can also surface existing discussions on any page with the Forum Feed and Trending blocks (and their [jetonomy_recent_posts] / [jetonomy_trending_posts] shortcodes) - see Shortcodes, Widgets & Blocks.

These Pro extensions add to the discussion experience:

  • Polls - attach a single or multi-select poll to any topic.
  • Reactions - emoji reactions on posts and replies.

Replies are where conversations happen. Jetonomy's reply system supports threaded discussions, multiple sort orders, accepted answers in Q&A spaces, and efficient loading for threads with hundreds of contributions.

Single topic page with threaded replies and voting controls

What You Will Learn

  • How to add a reply to a topic
  • How threaded replies work and how deep they go
  • How to sort replies and what each sort does
  • How accepted answers work in Q&A spaces
  • How to edit your own replies
  • How Jetonomy handles large threads efficiently

The Reply Composer

The reply composer appears at the bottom of every topic page. Click into the text area to expand the full Markdown toolbar - the same formatting options available in the post composer (bold, italic, inline code, links, block quotes, image upload, code blocks).

Click Reply to submit. A new top-level reply is added in place without a page reload. A threaded sub-reply (a reply to another reply) reloads the page so it slots into the right position under its parent.

If the space has "Require Post Approval" enabled, your reply is held for moderator review before appearing to other members. A confirmation message tells you it is pending.

Quote Replies

To reply while quoting a specific passage from an existing reply, select the text you want to quote and click the Quote button that appears. Jetonomy inserts the quoted text as a styled blockquote in your reply composer, with the author attribution linked back to the source reply.

You can also click Quote in the ... menu on any reply to quote its full body without selecting text first. This is the quickest way to address a specific point from a long reply - the quoted passage gives readers the context without forcing them to scroll back up.

Threading: Replies to Replies

You can reply to any existing reply by clicking the Reply link that appears when you hover over a reply. This creates a threaded sub-reply nested visually under the parent.

Jetonomy supports three levels of nesting:

Reply (level 1)
  └─ Reply to reply (level 2)
       └─ Reply to reply to reply (level 3)

At level 3, no further nesting is allowed. Members can still reply to a level-3 comment - that reply is added at level 3 as well, keeping the conversation readable.

Threaded replies let you have multiple parallel conversations inside the same topic without them colliding. A question inside a reply gets its own sub-thread. The main discussion continues underneath.

Sorting Replies

Use the sort controls at the top of the reply list to change the order:

Oldest first - Replies appear in chronological order, oldest at the top. Best for reading a long discussion from the beginning. This is the default for Forum spaces.

Newest first - Most recent replies appear at the top. Best for active topics where you want to see the latest contributions without scrolling.

Best - Replies are ranked by net vote score (upvotes minus downvotes), highest at the top. Best for Q&A spaces or any topic where you want the most useful contributions visible first. Within the same vote score, older replies rank first.

Sort preference is stored per-session - if you change it on one topic, it persists as you navigate between topics in the same session.

Note: "Best" is the vote-ranked sort for replies inside a topic. The space topic listing uses a similarly named vote-ranked sort called Popular (see Voting & Reputation). Both rank by net vote score - they are simply labelled differently because one orders replies and the other orders topics.

Accepted Answers in Q&A Spaces

In Q&A spaces, the person who asked the question (the topic author) decides which reply is the accepted answer. This signals to everyone else that the question has been solved and surfaces the winning reply at the top of the thread.

Q&A question with an accepted answer pinned to the top, showing the green Accepted tag and the Accepted answer callout box

Marking an Answer as Accepted

As the asker, you are the one who confirms which reply actually solved your problem:

  1. Open your own question. You must be the post author (space moderators and admins can also accept on your behalf, see below).
  2. Find the reply that best answers your question.
  3. Click the Accept button, the checkmark icon shown below the reply, on each reply in a Q&A space.
  4. The reply is immediately pinned to the top of the reply list, above all other replies and regardless of the current sort order.

What changes the moment you accept:

  • The reply gets a green Accepted tag and the whole thread is marked as resolved.
  • An "Accepted answer" callout box appears at the top of the topic, so anyone landing on the question sees the solution first without scrolling.
  • The reply's author receives a notification and a reputation bonus (a default of +15; you do not earn reputation for accepting your own reply). The bonus amount is admin-configurable under Settings → Permissions → Reputation Points.

Changing or Removing the Accepted Answer

The acceptance is never locked in. As the asker you stay in control:

  • Switch answers: Click Accept on a different reply. The previous reply automatically loses its accepted status, so there is only ever one accepted answer at a time.
  • Unaccept entirely: On the currently accepted reply, click the Unaccept button (the x-circle icon). This clears the accepted answer, returns the question to the unresolved state, removes the callout, and revokes the reputation that was awarded when it was accepted, so trust scores stay honest.

Tip: Space moderators (and admins) can also accept or unaccept answers on any Q&A topic in their space, useful for resolving questions on behalf of an asker who never came back to mark a solution.

Editing Your Own Replies

Click the ... menu on any reply you authored and select Edit. The reply text becomes an inline editor - you make changes and click Save. No page reload.

Edits are tracked as revisions internally. Moderators can view reply revision history from the moderation panel.

You can edit a reply at any time after posting. There is no edit window.

If a moderator edits your reply, the reply gets an "Edited by moderator" label.

How Jetonomy Handles Large Threads

For topics with many replies, Jetonomy uses cursor-based pagination to load replies in batches.

The first time you open a topic, you see the first batch of top-level replies (default: 20). A Load more replies button appears at the bottom if more exist. Clicking it loads the next batch without reloading the page.

For very high-traffic topics - those with hundreds of top-level replies - Jetonomy uses a smart loading strategy: it loads the first 10 replies and the last 10 replies, with a collapsed gap in the middle showing how many replies are hidden. You can click the gap to load replies from that range.

Threaded sub-replies follow the same pattern. A thread deeper than a few replies shows a "Show X more replies" link inline.

Note: New reply notifications appear as a sticky banner at the bottom of the page when other members post while you are reading. Click the banner to load the new replies without losing your scroll position.

What's Next?

Learn how upvotes and downvotes work, how they affect reputation, and how they power the Popular sort and trending sidebar.

Voting & Reputation →

Voting is the engine behind Jetonomy's quality signals. It surfaces the best content, rewards helpful members, and gives you a community where the most useful posts rise to the top naturally, without moderator intervention.

Topic page showing upvote and downvote buttons with vote scores on replies

What You Will Learn

  • How to vote on topics and replies
  • How vote scores appear in listings and on reply cards
  • How votes translate into reputation points
  • How votes power the Popular sort and trending sidebar
  • The one-vote-per-item rule and how to undo a vote

Voting on Topics and Replies

Every topic and every reply has an upvote button (thumbs up) and a downvote button (thumbs down). The net vote score (upvotes minus downvotes) is displayed between them.

To vote: Click the upvote or downvote button. The score updates instantly. No page reload.

To undo a vote: Click the same button again. The vote is removed and the score returns to its previous value.

To change your vote direction: Click the opposite button. Jetonomy removes your previous vote and applies the new one in a single action. The score adjusts correctly.

You must be logged in to vote. If you click a vote button while logged out, Jetonomy sends you to the WordPress login page and returns you to the same topic once you sign in.

Note: You cannot vote on your own posts or replies. The vote buttons are visible but disabled with a tooltip explaining why.

Note: When a space admin disables voting on a space, vote controls are removed from the interface entirely, not just greyed out. Members will not see the upvote or downvote buttons on any post or reply in that space.

Where Vote Scores Appear

Topic listing page - Each topic card shows the net vote score. This is how members quickly identify high-quality discussions without opening them.

Single topic page - The topic's vote score appears prominently alongside the title.

Reply cards - Every reply in a thread shows its vote score. In Q&A spaces, this score directly determines the Best sort order, making it easy to find the most helpful answer.

Vote scores are updated in real time as you vote. You never need to reload the page to see the current score.

How Votes Affect Reputation

When you receive votes on your content, your Jetonomy reputation score changes. Reputation is displayed on your public profile and powers your trust level progression.

Event Reputation change
Your post is upvoted +10
Your reply is upvoted +5
Your reply is accepted as an answer (Q&A) +15
Your post or reply is downvoted -2
Your post is deleted by a moderator -20

These are the defaults. Every value is admin-configurable per site under Jetonomy → Settings → Permissions → Reputation Points - positive numbers reward, negative numbers penalize, and any row you leave alone keeps its default. See Permissions Settings → Reputation Points for the full table and how to change a value.

Reputation is calculated and stored in real time. The moment someone votes on your content, your reputation score updates. There is no batch processing.

Casting a downvote does not cost you any reputation points. Downvotes are free for voters.

How Votes Power Content Discovery

Votes are not just cosmetic. They feed two core discovery features:

Popular sort - On the space listing page, the Popular sort orders topics by their net vote score. High-vote topics stay visible longer. New topics start at zero and rise based on community reaction.

Trending sidebar - The community home page and individual space pages show a trending sidebar highlighting the topics with the highest recent vote velocity, meaning they received the most votes in a short window. A post with 20 upvotes in 2 hours outranks one with 100 upvotes spread over a month.

In Ideas spaces, vote scores are the primary ranking signal on the roadmap view. Ideas with the most votes appear first, giving product teams a clear priority signal.

The One Vote Per Item Rule

Each member can cast exactly one vote per post and one vote per reply. The database enforces this with a unique constraint. There is no way to vote twice on the same item.

Toggling (undoing) and changing direction both work correctly within this constraint. At any moment, your vote on a given item is either +1, -1, or nothing.

If you vote on a topic in the listing view, then open the topic and vote again, you are interacting with the same vote record. The second click undoes the first vote.

What's Next?

Learn how to bookmark topics for quick access and how to follow spaces to get notified about new posts.

Bookmarks & Following →

Bookmarks and following are how members stay connected to the content they care about. Bookmarks are a personal reading list. Following a space is a subscription to that space's activity. Together they make sure your community members never lose track of an important discussion.

Topic page with bookmark, follow, and share action buttons

What You Will Learn

  • How to bookmark a topic and access your bookmarks later
  • How to remove a bookmark directly from the My Bookmarks page
  • How to follow a space and what that means for notifications
  • How auto-subscription works when you post or reply
  • How to manage your subscriptions and turn off notifications

Bookmarking Topics

How to Bookmark

Open any topic and click the bookmark icon in the topic action bar (next to the share and report buttons). The icon fills to indicate the bookmark is saved. Click it again to remove the bookmark.

Bookmarking is instant and does not require a page reload. There is no confirmation dialog.

Accessing Your Bookmarks

Two ways to get there, both show the same list:

  • The Bookmarks tab on your profile at /community/u/your-username/
  • A dedicated standalone page at /community/bookmarks/ (new in 1.4.1)

Both list every topic you have bookmarked, most recent first. Each item shows the topic title, the space it belongs to, the author, and when it was posted. Click any item to go directly to the topic.

Bookmarks are private - other members cannot see your bookmark list. The standalone page requires sign-in and is excluded from search engines so the URL itself never reveals what you've saved.

Removing a Bookmark from the Bookmarks Page

Each card on the My Bookmarks page carries a bookmark icon button in the top-right corner. Clicking it removes the bookmark immediately - no confirmation dialog. The card fades out and disappears from the list so the page always reflects your current bookmark state.

The same toggle works in reverse too: if you accidentally remove a bookmark, re-open the topic and bookmark it again from the topic action bar.

Tip: Use bookmarks as a reading queue - bookmark topics you want to read later, then clear them as you go. The Bookmarks tab gives you a clean list without any algorithm reordering it.

Following Spaces

How to Follow

Visit any space and click the Follow button in the space header. The button changes to Following immediately.

Following a space subscribes you to new post notifications from that space. Every time a new topic is published in a space you follow, you receive a notification in your notification bell.

Unfollowing

Click Following in the space header to toggle it off. The button returns to Follow and you stop receiving new post notifications from that space.

Following a space does not make you a member of the space. In Open spaces, you can follow without joining - you will receive notifications but you will not be listed as a member and cannot post until you join.

What You Get Notified About

When you follow a space, you receive notifications for:

  • Every new topic published in that space (including by authors you do not know)

You do not receive notifications for replies to topics in followed spaces unless you are also subscribed to those individual topics. Following is space-level; subscriptions are topic-level.

Topic Auto-Subscription

When You Create a Topic

When you publish a new topic, you are automatically subscribed to it. You will receive notifications for:

  • Every new reply to your topic (including threaded sub-replies)
  • When your topic is answered and accepted (Q&A spaces)
  • When your topic's idea status changes (Ideas spaces)

When You Reply to a Topic

When you post a reply to any topic, you are automatically subscribed to that topic. You will receive the same notifications as if you had subscribed manually.

Unsubscribing from a Topic

Open the topic and click the ... menu at the top of the page, then select Unsubscribe. You will stop receiving notifications for new replies on that topic.

You can re-subscribe at any time using the same menu.

Managing All Your Subscriptions

Go to your profile's Settings page at /community/u/your-username/edit/ and open the Notifications section.

Here you can:

  • See a list of all spaces you follow with a one-click unfollow option
  • See topics you are subscribed to (the list shows the 20 most recent)
  • Set your notification preference for email delivery (immediate, daily digest, or never)

Note: Daily and weekly email digests are a Jetonomy Pro feature. In the free plugin, email notifications are sent immediately for each event or not at all - based on your notification settings.

What's Next?

Learn how to save work-in-progress posts as drafts and schedule topics for future publication.

Drafts & Scheduled Posts →

Not every post is ready to publish the moment you start writing it. Drafts let you save work-in-progress posts and come back to them later. Scheduled posts let you write now and publish at a specific date and time automatically.

Post composer with draft and schedule publishing options

What You Will Learn

  • How to save a post as a draft
  • Where to find and manage your drafts
  • How to schedule a post for future publication
  • How the draft/schedule split-button UI works
  • How scheduled posts are published and what happens if the window is missed

Saving a Draft

When you are composing a new topic and want to save your progress without publishing:

  1. Write your title and content as usual.
  2. Click the arrow next to the main Post button to expand the split-button menu.
  3. Select Save as Draft.

Your draft is saved immediately and you are returned to the space listing. No notification is sent to other members. The topic does not appear in the space listing.

Tip: Press Ctrl+Enter (Windows/Linux) or Cmd+Enter (Mac) to trigger the composer's primary action without reaching for the mouse. This clicks the split-button's current primary button - for a new post that submits it; while editing a draft it runs whichever action the button currently shows.

Finding Your Drafts

Two ways to get there, both show the same list:

  • The Drafts tab on your profile at /community/u/your-username/
  • A dedicated standalone page at /community/drafts/ (new in 1.4.1)

Your drafts are listed in order of last-modified time, most recent first. The standalone page requires sign-in and is excluded from search engines, so unfinished work never leaks. Each draft shows:

  • The post title (or "Untitled Draft" if you have not written a title yet)
  • The space it is intended for
  • When you last saved it

Click any draft to reopen the full composer with your saved content. Make your changes, then publish or save again.

Drafts are private. Only you and site admins can see your drafts.

Drafts and Post Counts

Draft posts do not count toward any community statistics until they are published:

  • They do not increment the space's post count.
  • They do not appear in the space listing.
  • They do not appear in search results.
  • They do not appear on your public profile's Posts tab.

The moment a draft is published, either manually or via scheduled publishing, all of the above update immediately.

Scheduling a Post

You can schedule a post to publish automatically at a specific future date and time:

  1. Write your title and content.
  2. Click the arrow next to the Post button to expand the split-button menu.
  3. Select Schedule.
  4. A date and time picker appears. Choose when you want the post to go live.
  5. Click Schedule Post.

The post is saved with a Scheduled status. It does not appear publicly until the scheduled time.

Scheduled posts appear in your Drafts tab with a "Scheduled" label and the publish date/time displayed. You can click into a scheduled post to edit the content or change the publish time at any point before it goes live.

To cancel a scheduled post and convert it back to a draft, open it and click Unschedule.

How Scheduled Publishing Works

Jetonomy publishes scheduled posts through Action Scheduler - the same background queue that powers WooCommerce - which runs its own queue runner rather than relying purely on page views. The check runs every hour. (WP-Cron remains a legacy fallback for older installs.)

This means a post scheduled for 9:00 AM may publish at any point during the following hour rather than exactly on the minute.

If a post's scheduled time is missed (for example, because the queue did not run during a low-traffic period), Jetonomy will publish it on the next run. It never silently drops a scheduled post.

The Split-Button UI

Post composer publish split-button expanded, showing Save as Draft and Schedule options with the date and time picker open

The publish button in the post composer is a split button:

  • Left side: The primary action (defaults to Post Now for new posts, Update for existing drafts).
  • Right side (arrow): Opens the options menu with Save as Draft, Schedule, and any other contextual actions.

The left side updates based on context. When you are editing a draft, the left side shows Publish Now and the right side shows options to reschedule or keep as draft.

What's Next?

Learn the full set of moderation tools available to space moderators: moving, merging, splitting, pinning, closing, and deleting topics.

Topic Management →

Space moderators have a full toolkit for organizing, curating, and controlling discussions. Every action in this guide requires moderator permission on the space where the topic lives - or site-wide admin access.

Admin moderation panel with content review and bulk action controls

What You Will Learn

  • How to move a topic to a different space
  • How to merge duplicate topics
  • How to split a reply into a new topic
  • How to pin and unpin topics
  • How to close and reopen topics
  • How to delete topics

Who Can Use These Tools

All moderation actions described here require one of the following:

  • Space Moderator role in the space where the topic lives
  • Space Admin role in that space
  • WordPress Administrator (can moderate any space)

Regular members do not see moderation options in the menus, even if they are the topic author. Topic authors can edit and delete their own posts, but not pin, close, move, merge, or split them.

Moving a Topic to a Different Space

Use this when a topic was posted in the wrong space - for example, a bug report in a General Discussion space that belongs in a Support space.

  1. Open the topic.
  2. Click the ... menu at the top right of the topic.
  3. Select Move Topic.
  4. A modal appears with a searchable space picker. Type to filter spaces by name.
  5. Select the destination space.
  6. Click Move.

The topic disappears from the original space immediately and appears at the top of the destination space's listing. Its URL changes to reflect the new space slug. The old URL redirects to the new one automatically.

All replies, votes, bookmarks, and subscriptions move with the topic. Nothing is lost.

Tip: If you move a Q&A topic from a Q&A space into a Forum space, the accepted answer marking is preserved in the database, but the "Accepted" badge may not render in the Forum space depending on your display settings.

Merging Duplicate Topics

Use this when two members post the same question or idea in the same space. Merging moves all replies from the source topic into the target topic and deletes the source.

  1. Open the topic you want to remove (the duplicate).
  2. Click the ... menu and select Merge Topic.
  3. A modal appears with a search field. Search for the target topic by title.
  4. Select the target topic.
  5. Click Merge.

All replies from the duplicate are appended to the target topic's reply list in chronological order. The source topic is permanently deleted - it is not moved to trash. A moderator note is added to the target topic indicating that replies were merged from another topic.

Warning: Merging cannot be undone. Verify you have selected the correct target topic before confirming.

Splitting a Reply Into a New Topic

Use this when a reply inside a topic starts a new conversation that deserves its own thread.

  1. Find the reply you want to split.
  2. Click the ... menu on that reply.
  3. Select Split to New Topic.
  4. A modal appears asking for the new topic title.
  5. Enter the title and click Split.

Jetonomy creates a new topic in the same space with your chosen title. The selected reply becomes the first post content of the new topic. All sub-replies (direct children of that reply) move with it.

The original reply is removed from the source topic. A moderator note appears in the source topic: "A reply was split into a new topic: [title with link]."

Pinning and Unpinning Topics

Pinned topics appear at the top of the space listing, above all other topics, regardless of which sort tab is selected (Latest, Popular, or Unanswered). Use pinning for space rules, important reference threads, or a "start here" topic. Pinning here affects only this space - to feature a post across the whole community, see Community Announcements (Pro, admins only).

  1. Open the topic.
  2. Click the ... menu and select Pin.

The topic moves to the top of the listing immediately and shows a green Pinned badge - on both the listing row and the topic's own header - so members can see at a glance that it is pinned.

To unpin, open the same menu and select Unpin.

Each space allows up to 3 pinned topics by default. Once the limit is reached, pinning another topic returns "You can pin up to 3 topics in a space. Unpin one first." The cap keeps the top of the space scarce and meaningful; developers can change it with the jetonomy_max_space_pins filter.

Closing Topics

Closing a topic prevents new replies while keeping all existing content visible and readable. Use this when a question has been answered, a discussion has run its course, or a thread is becoming unproductive.

  1. Open the topic.
  2. Click the ... menu and select Close Topic.

Closed topics show a banner: "This topic is closed. No new replies can be added."

The reply composer is hidden for regular members. Moderators and admins can still reply to closed topics.

To reopen a topic, click the ... menu and select Reopen Topic.

Deleting Topics

Deleting a topic moves it to trash. It disappears from the space listing and is no longer accessible to members. The space's post count decrements.

  1. Open the topic.
  2. Click the ... menu and select Delete Topic.
  3. Confirm the deletion in the prompt.

Deleted topics can be recovered by a site admin from Jetonomy → Content in the WordPress admin - filter by status "Trash" to find them. A trashed topic can be restored or permanently deleted.

Permanent deletion removes the topic, all replies, all votes, all bookmarks, and all associated notifications from the database. It cannot be undone.

Note: When you delete a topic, the topic author's reputation is reduced (a default of 20 points) to reflect the removed content. The penalty amount is admin-configurable under Settings → Permissions → Reputation Points.

What's Next?

Return to the Spaces & Categories section to learn about managing space membership and access rules.

Membership & Join Policies →

Two features that give you finer control over individual topics - mark sensitive topics private so only you and moderators can see them, and use colored prefixes to classify topics at a glance in the space listing.

What You Will Learn

  • How to mark a topic as private and who can read it
  • How to enable topic prefixes for a space
  • How to create, edit, and color prefixes
  • How prefixes appear in the space listing and topic page
  • When to choose a private topic over a private space

Private Topics

A private topic is visible only to its author and to space moderators. Other members of the space cannot see it in the listing, cannot find it through search, and cannot open its URL directly - they receive a "not found" response instead.

Use private topics for:

  • Support requests that include account details, order numbers, or personal information
  • Reports of abuse or harassment where naming the other member in public would escalate the situation
  • Personal asks to a space owner that do not need a full private message thread
  • Sensitive billing or legal questions where a full support ticket is too formal

Enabling Private Topics on a Space

Private topics are an opt-in feature per space. The space owner turns them on from Jetonomy → Spaces → (your space) → Settings → Posting.

  1. Toggle Allow private topics on.
  2. Save.

If the setting is off, the Private toggle does not appear in the new post composer and members cannot create private topics in the space.

Creating a Private Topic

When the feature is enabled for a space, a Private toggle appears at the bottom of the new post composer. Turn it on before submitting and your topic is flagged as private.

Private topics show a Private badge next to the title on the topic page. The badge is visible only to you and to moderators (other members cannot see the topic at all).

Who Can See Private Topics

Role Can see private topics?
Topic author Yes
Space moderators Yes
Space owner Yes
WP admins Yes
Other space members No
Logged-out visitors No

A moderator can reply to a private topic, mark it resolved, or escalate it to a full support ticket using whatever workflow your community has. The topic author is notified of replies the same way they would be on a normal topic.

Note: Private topics are different from private spaces. A private space is hidden entirely - a member either has access to everything in it or nothing at all. A private topic is a one-off exception inside an otherwise public space. Pick private spaces for ongoing confidential work (staff lounges, paying customer lounges) and private topics for one-off sensitive conversations.

Topic Prefixes

Prefixes are colored labels that appear in front of topic titles in the space listing. They let members classify topics at a glance - Bug, Question, Solved, Announcement, Discussion, and so on.

Prefixes are configured per space by the space owner. Different spaces can have entirely different prefix sets - a support space might use Bug, Question, Solved, while a marketing space might use Idea, Campaign, Report.

Enabling and Creating Prefixes

Go to Jetonomy → Spaces → (your space) → Settings → Prefixes.

  1. Toggle Enable topic prefixes on.
  2. Click Add Prefix.
  3. Type a short label (up to 20 characters).
  4. Pick a color from the palette, or enter a custom hex value.
  5. Save.

Repeat for each prefix you want. Re-ordering a prefix in the admin changes its order in the composer picker. Prefixes you no longer need can be deleted - topics that used a deleted prefix revert to having no prefix.

Using a Prefix When Creating a Topic

When prefixes are enabled for a space, a Prefix dropdown appears next to the title field in the new post composer. Members pick one prefix per topic, or leave it blank.

If the space is configured with Require prefix enabled, the composer rejects the submission unless a prefix is selected.

Where Prefixes Appear

  • In the space listing next to the topic title
  • On the topic page, in the topic header above the title
  • In search results
  • In notification emails that reference the topic
  • In the admin moderation queue

Filter the space listing by prefix by clicking any prefix label - the listing re-renders to show only topics with that prefix.

Prefix Color Guidelines

  • Use high-contrast colors for critical prefixes (Bug in red, Announcement in blue)
  • Use muted colors for passive prefixes (Discussion in grey)
  • Avoid using green except for Solved or Done - green is a strong "resolved" signal
  • Avoid using red except for critical prefixes - red grabs attention

What's Next?

Return to Creating Topics for a full walkthrough of the new post composer, or continue to the next discussion guide.

The composer is the box you type in when you create a new post or reply. It works like a modern community tool, not a plain comment form. Members can @mention each other, see at a glance which threads have new replies, and find out who runs a space and who on staff is replying. This guide covers each of those features and how they behave for the member.

What You Will Learn

  • How @mention autocomplete works in posts and replies
  • What the "New" pill on the topic card means and when it clears
  • How the "Managed by" sidebar surfaces space admins and moderators
  • Where role pills appear and what they tell members
  • How link previews turn a pasted URL into a rich card
  • How members add images, and which capability gates uploads
  • How the composer's formatting toolbar and markdown shortcuts work

@mention Autocomplete

Type @ anywhere in the composer and Jetonomy opens a dropdown of matching people. Keep typing to narrow the list, then click a result or press Enter to insert the mention.

The inserted mention is a clickable link to the user's profile, and the moment your post or reply is published, Jetonomy fires a notification to the mentioned member. They see it in the bell menu and, if they have email notifications on, in their inbox.

Where It Works

Surface Mentions supported
New post composer Yes
Reply composer Yes
Edit post / edit reply Yes
Private message composer (Pro) Yes
Quick reply on a topic card Yes

Who Shows Up in the Dropdown

The dropdown is scoped to people the current user shares at least one space with. This keeps the list relevant on large communities and avoids leaking member names across private spaces. Within that scope, results are ordered by:

  1. Recent contributors in the current space (they are most likely to be relevant)
  2. Other members of the current space
  3. People you share other spaces with

If you start typing a name that does not match anyone in your shared spaces, the dropdown shows "No matches" rather than searching the whole site.

Walkthrough: Mentioning a Member in a Reply

  1. Open any topic and start a reply.
  2. Type @ followed by the first few letters of the person's name or username.
  3. The dropdown appears under your cursor with up to 10 matching members.
  4. Use the arrow keys to highlight a name, or click directly.
  5. Press Enter (or click) to insert the mention.
  6. Publish the reply. The mentioned member receives a notification within seconds.

Notes on Privacy

  • Mentions in a private space still notify the mentioned member, but the linked post is only visible to people who can read that space.
  • Mentioning a member who has muted you still inserts the link, but no notification is sent.
  • Site admins can disable mention notifications globally in Jetonomy → Settings → Notifications.

"New" Pill on Unread Threads

The space listing shows a card for each topic. When a topic has replies you have not read yet, a small "New" pill appears on the card.

The pill is intentionally subtle. It uses the --jt-accent design token, so it picks up your theme's brand color and matches dark mode automatically.

When the Pill Appears and Clears

Event Pill state
Someone replies to a thread you previously read Pill appears
You open the thread Pill clears immediately
You scroll past the thread without opening it Pill stays
The original poster edits their post (no new replies) Pill does not appear
You are not signed in Pill never appears (read state is per-user)

The pill is driven by the same read-status table that powers the unread indicator in the bell menu. There is no separate setting to toggle it. If you do not want unread tracking at all, disable read tracking in Jetonomy → Settings → Performance.

Mobile Behavior

On mobile the pill sits at the top-right of the card, sized for thumb readability without crowding the title. It uses the same touch target as the rest of the card, so tapping anywhere on the card opens the thread and clears the pill.

"Managed by" Sidebar Card

Space sidebar "Managed by" card listing the space admins and moderators with avatars and role badges

Every space page now shows a "Managed by" card in the sidebar. The card lists the space admin(s) and moderator(s) with their avatars and a small role badge next to each name.

What the Card Shows

  • Up to 5 staff members per space, sorted with admins first, then moderators
  • Each entry shows: avatar, display name, role badge
  • Hovering an entry reveals a "View profile" link
  • If the space has private messaging enabled (Pro), a "Message" link appears as well

If a space has more than 5 staff, the card shows the first 5 and a "See all staff" link that opens the full member list filtered to staff only.

Why This Helps Members

Members often want to ask a question, report a problem, or appeal a moderation action but don't know who to talk to. The "Managed by" card answers that question on every space page, without forcing members to dig through settings or member lists.

For owners, this surfaces your community staff and gives them visibility. Members are more likely to recognise and trust moderators they see consistently.

Customizing the Card

The card is rendered from templates/partials/managed-by-card.php. You can override it in your theme at theme/jetonomy/partials/managed-by-card.php to change the layout, hide certain roles, or add extra links.

Role Pills on Posts and Replies

In addition to the sidebar card, admins and moderators now have role pills next to their name on every post and reply they make. The pill is small, accessible, and uses theme tokens for color.

Role Pill label Pill style
Community Admin "Admin" Filled, accent color
Space Moderator "Mod" Outline, accent color
Member (no pill) n/a

The pills are visible to everyone, including signed-out visitors. They make it obvious when an answer comes from staff vs from another member, which matters for support spaces and Q&A spaces where members weigh staff replies more heavily.

Paste a URL on its own line in a post or reply and Jetonomy automatically fetches the page's metadata and renders a rich preview card beneath it, the same style of card you see when you share a link on LinkedIn or Twitter. Members get context (title, description, thumbnail, site name) without leaving the thread.

How It Works

When a link sits alone on its own line (not inline inside a sentence), Jetonomy calls GET /jetonomy/v1/link-preview?url=... and renders the result as a card. The card shows:

  • Thumbnail image (when the page provides one)
  • Page title
  • Short description
  • Site name and favicon

The preview endpoint pulls metadata from Open Graph and Twitter Card tags, so it covers almost any site with social tags (news outlets, blogs, GitHub, LinkedIn, Instagram, Facebook, and more). For the major sanctioned oEmbed hosts (YouTube, Vimeo, TikTok, Spotify, SoundCloud, Reddit, and the rest of the WordPress oEmbed registry) it returns a true rich embed instead of a static card.

Behavior and Limits

  • Up to 3 preview cards render per post or reply, so a message full of links does not turn into a wall of cards.
  • Results are cached on the server (roughly 12 hours for a successful fetch, a few minutes for a failed one), so a 200-reply thread does not refetch the same link 200 times.
  • Mentions (@name) and tag links never turn into preview cards, only standalone web URLs do.

Developers can customize how link previews are fetched and rendered - see Developer Notes at the end of this guide.

Media Uploads

The composer lets members add images to posts and replies in three ways:

  • Toolbar button - click the image button in the composer toolbar to open a file picker.
  • Drag and drop - drag an image straight from your desktop onto the editor.
  • Paste - paste a screenshot or copied image directly from your clipboard.

Uploads go to POST /jetonomy/v1/media, which stores the file as a standard WordPress attachment and returns its URL so the image drops into the editor inline. Every uploaded image is given alt text automatically (derived from the file name) for accessibility and SEO, and members can supply their own alt text.

Who Can Upload

In practice, most members who can post or reply can also attach images. Members who can already upload to your WordPress media library can attach images, and members earn the ability to upload automatically once they reach Trust Level 1, even if their WordPress role would not otherwise allow it. So a brand-new member may not be able to attach an image on their very first day, but gains it as soon as they reach Trust Level 1.

Developers who need the exact capability list can find it under Developer Notes.

The Formatting Toolbar

The composer is a single rich-text editor with a small formatting toolbar - there is no separate "plain text" vs "WYSIWYG" mode to choose between. The toolbar buttons are: bold, italic, inline code (</>), link, blockquote, and image upload.

Members who prefer to type can also use common markdown shortcuts (**bold**, *italic*, `code`, > quote) directly in the editor. The toolbar and the markdown shortcuts produce the same formatted result.

Paste handling, image uploads, and @mention autocomplete all work the same way regardless of whether you click a toolbar button or type the markup yourself.

For more on the content editor and its fields, see Creating Topics.

Developer Notes

These details are for developers extending the composer. Site owners and members do not need them.

The link preview pipeline is fully filterable:

Filter Purpose
jetonomy_link_preview_providers Add or reorder host-specific providers (e.g. a custom intranet URL rewriter) ahead of the defaults
jetonomy_link_preview_data Final mutation of the preview data right before it is cached and returned
jetonomy_link_preview_cache_ttl Override the cache lifetime, in seconds
jetonomy_link_preview_user_agent Override the user agent used when fetching the page (some corporate firewalls only allow specific agents)

Media upload capabilities

Uploading is gated by capability. A member can upload media if they have any one of:

  • upload_files - the standard WordPress capability (Author and above)
  • jetonomy_upload_media - the Jetonomy role-map capability, granted to the Contributor role and automatically granted at Trust Level 1
  • jetonomy_create_posts or jetonomy_create_replies - anyone who can post or reply

Uploads go to POST /jetonomy/v1/media, which stores the file as a standard WordPress attachment and returns its URL.

What's Next?

Learn how Jetonomy's in-product modal toolkit replaces native browser dialogs with accessible, themeable confirmations.

Modals and Confirmations

Jetonomy 1.4.0 introduced an in-product modal toolkit that replaces the native browser confirm, alert, and prompt dialogs across the community. Native browser dialogs look like operating system pop-ups, cannot be themed, and don't follow modern accessibility patterns. The Jetonomy modals are themed, keyboard-accessible, and localised. The 1.4.2 retag completed full translation coverage for the button labels.

What You Will Learn

  • What the modal toolkit replaces and why
  • Where you'll see Jetonomy modals in the community
  • How the modals handle accessibility and keyboard navigation
  • How button labels translate for non-English sites
  • How dark mode and theme colors are picked up automatically
  • How to override the modal styling in your theme

What the Toolkit Replaces

JavaScript has three built-in dialog functions:

  • window.confirm("Are you sure?") returns true / false
  • window.alert("Done.") shows an info message
  • window.prompt("Reason?") asks for a text input

These look like system pop-ups. They sit outside your theme, look different on every operating system and browser, can't be styled, and don't return focus to the right element when dismissed. Screen readers treat them inconsistently, and keyboard navigation is at the mercy of the browser.

Jetonomy now uses its own modal toolkit for every "Are you sure?" prompt across the plugin. The modals look like your community, behave consistently, and play nicely with screen readers and keyboards.

Themed danger confirmation modal reading "Delete this post?" with a red Delete button and a Cancel button

Where You'll See Jetonomy Modals

Anywhere the community needs to confirm an action or collect a short answer:

Action Modal type
Delete a post or reply Confirm
Remove a flag without taking action Confirm
Ban or silence a member Prompt (asks for duration / reason)
Lift a ban Confirm
Move a topic to another space Prompt (asks for target space)
Split a reply into a new topic Prompt (asks for new topic title)
Pin or unpin a topic Confirm
Mark all notifications read Confirm
Trash a category Confirm
Approve or reject pending content Confirm
Cancel an unsaved edit Confirm

The pattern is consistent: destructive actions always require a confirmation, prompts that need an answer always show a clear input field with sensible defaults.

The toolkit exposes three modal types:

  • Confirm - two buttons (Cancel / Confirm). For yes/no decisions
  • Alert - one button (OK). For one-way info or success messages
  • Prompt - text input plus two buttons (Cancel / Submit). For collecting a short answer

Each type uses the same overlay and box, so the visual style is uniform across the community.

Accessibility

The toolkit is built to WCAG 2.1 AA. Every detail that screen reader users and keyboard users rely on is wired up:

Feature Behaviour
Keyboard open Focus moves to the first focusable element in the modal
Tab cycling Focus stays trapped inside the modal until it closes
Escape key Dismisses the modal, equivalent to clicking Cancel
aria-labelledby Points to the modal title element
aria-describedby Points to the modal body text
role="dialog" Set on the modal box
aria-modal="true" Tells assistive tech the rest of the page is inert
Focus return When the modal closes, focus returns to the element that opened it
Overlay click Closes the modal (configurable, off for destructive prompts)

Screen reader users hear the modal title and body when it opens. Keyboard users can tab through the controls without escaping the dialog. Mouse users can dismiss by clicking the overlay, except on destructive prompts where overlay dismiss is intentionally disabled to prevent accidental data loss.

Reduced Motion

If the visitor's operating system has "Reduce motion" enabled, the modal skips its slide-in and fade animations. The dialog still opens and closes; it just appears and disappears instead of animating. This respects vestibular accessibility preferences without compromising functionality.

Localisation

All button labels translate through the standard WordPress translation pipeline:

English label Used in
Cancel Every modal
Confirm Confirm modals
OK Alert modals
Submit Prompt modals

Modal titles and body text are localised by the calling code (e.g. "Delete this post?" is translated by the delete action that opens the modal). The toolkit itself only contributes the four button labels above.

The 1.4.2 retag completed full translation coverage for every label. Before that retag, English fallbacks could leak into translated locales for one or two of the buttons. Sites running a translated locale (French, German, Spanish, Arabic, etc.) now see fully translated buttons everywhere.

Dark Mode

The modal box and overlay both use Jetonomy design tokens. That means:

  • Dark theme active → modal renders dark automatically
  • Light theme active → modal renders light
  • Theme brand color → primary button picks it up
  • Theme border radius → modal corners match

No per-theme configuration required. If your theme defines a custom --jt-bg or --jt-accent, the modal picks it up.

Styling Override

Site owners who want a custom look can override the modal styles in their theme stylesheet. The relevant classes are:

Class Element
.jt-modal-overlay The backdrop behind the modal
.jt-modal-box The modal container
.jt-modal-title The title heading inside the modal
.jt-modal-body The body text or prompt input wrapper
.jt-modal-actions The button row at the bottom
.jt-modal-button Both buttons (Cancel / Confirm)
.jt-modal-button--primary The primary (right-hand) button
.jt-modal-button--secondary The Cancel (left-hand) button

Example: dimming the overlay further for a more cinematic feel.

.jt-modal-overlay {
  background: rgba(0, 0, 0, 0.7);
}

Example: making the modal a bit wider on desktop.

@media (min-width: 768px) {
  .jt-modal-box {
    max-width: 560px;
  }
}

We recommend overriding tokens (e.g. --jt-bg, --jt-radius) at the modal level rather than rewriting properties directly, so dark mode and theme switching keep working.

Developer Note

The toolkit exposes three global functions for any custom JavaScript that integrates with Jetonomy. Each returns a Promise.

The message is always the first argument; an options object follows.

// Yes/no confirmation
const confirmed = await window.jetonomyConfirm( 'This cannot be undone.', {
  title: 'Delete this post?',
  confirmLabel: 'Delete',
  danger: true,
} );

// One-button info dialog
await window.jetonomyAlert( 'Your changes are live.', {
  title: 'Saved',
} );

// Text input prompt
const reason = await window.jetonomyPrompt( 'Tell our moderators what is wrong.', {
  title: 'Why are you reporting this?',
  placeholder: 'Reason',
} );

The danger: true flag styles the confirm button in red and disables overlay-click dismiss. Useful for delete confirmations and account actions.

These are pure JS globals; you don't need to import anything if jetonomy is already enqueued on the page.

What's Next?

Learn how the activity log tracks every audit-worthy event in your community.

Activity Log

Members normally start a topic from inside a space. But you can also drop a "start a topic" box onto any WordPress page, post, or landing page - so a visitor can begin a discussion without first navigating into the community. Jetonomy gives you two ways to do this, and both produce the same box: a Gutenberg block and a shortcode.

Compose Topic box embedded on a regular WordPress page, with a space picker, title field, and details field

What You Will Learn

  • Where the embedded composer is useful
  • How to add it with the Compose Topic block (Gutenberg)
  • How to add it with the [jetonomy_compose_topic] shortcode (Classic editor and page builders)
  • The difference between "picker" mode and "fixed" mode
  • What logged-out visitors see

When to Use It

The embedded composer is handy when you want members to start a discussion from outside the community pages, for example:

  • A "Ask the community" box on your homepage or a landing page
  • A support page that drops members straight into your Support space
  • A product page that invites feedback into an Ideas space

Adding It with the Block (Gutenberg)

  1. Edit any page or post in the WordPress block editor.
  2. Click the + (Add block) button.
  3. Search for Jetonomy Compose Topic and insert it.
  4. In the block sidebar, choose how the box behaves (see Picker vs Fixed Mode below).
  5. Publish or update the page.

Adding It with the Shortcode

If you use the Classic editor, a widget, or a page builder (Elementor, Divi, Bricks, WPBakery), use the shortcode instead. It produces an identical box.

[jetonomy_compose_topic mode="picker"]
[jetonomy_compose_topic mode="fixed" space_id="5"]

The block and the shortcode are interchangeable - either surface works and they render the same composer, so pick whichever fits your editor.

Picker vs Fixed Mode

The composer has two modes, set with the mode attribute (block sidebar control or shortcode attribute):

Mode What it does
Picker (default) Shows a space dropdown so the member chooses which space their topic lands in. Only spaces where the member is allowed to post appear in the list.
Fixed Locks the box to one space - no dropdown. Use this on a page that is clearly about a single topic area (e.g. a Support page that always posts into the Support space). Set the space with space_id (the Space ID to post into control in the block).

If you choose Fixed mode but the space is missing or the member cannot post in it, the box quietly falls back to Picker mode rather than showing an error.

Limiting the Post Types

The types attribute controls which kinds of topic the box can create. By default it allows topic,question,idea. Remove any you do not want to offer on that page. (The actual post type a member ends up creating still follows the space type - see Creating Topics.)

What Logged-Out Visitors See

If a visitor is not signed in, the box does not show a form. Instead it shows a "Sign in to start a new topic" prompt that returns them to the same page after they log in. There is no exposed form for signed-out visitors, so you can safely place the composer on a public page.

Every public space in Jetonomy publishes an RSS 2.0 feed. Members can subscribe in any feed reader and receive new discussions as they are published, without visiting the community.

What You Will Learn

  • The URL format for a space's RSS feed
  • Which spaces publish a feed and which do not
  • How feed readers auto-discover the feed
  • What each feed item contains

Feed URL

Each public space serves its feed at:

/community/s/{slug}/feed/

Replace {slug} with the space's URL slug - the same segment that appears in the space URL at /community/s/{slug}/. For example, the "Announcements" space at /community/s/announcements/ publishes its feed at /community/s/announcements/feed/.

If your community uses a custom base slug (configured under Jetonomy → Settings → General), replace community with that slug.

Which Spaces Publish a Feed

A feed is only served when two conditions are both true:

  1. The community visibility is set to Public (the global setting under Jetonomy → Settings).
  2. The space itself is Public - meaning an anonymous visitor can read it.

Private spaces and Hidden spaces do not publish a feed. Requesting /community/s/{slug}/feed/ for a private or hidden space returns a 404, not a feed. This ensures that gated content never leaks through a feed URL that gets shared or cached by an aggregator.

Auto-Discovery

When a visitor or browser extension loads a public space page, Jetonomy adds a <link> tag to the HTML <head> that points to the feed:

<link rel="alternate" type="application/rss+xml" title="Space Name - RSS" href="/community/s/slug/feed/" />

Feed readers and browser extensions that support auto-discovery (most do) detect this tag automatically. Members can subscribe by pasting the space URL into their feed reader - they do not need to know the /feed/ URL in advance.

Auto-discovery is only emitted on public spaces. Private and hidden space pages do not emit the <link> tag.

What Each Feed Contains

Each feed includes the 20 most recently published topics in the space, newest first. There is no pagination - feeds are a rolling snapshot of recent activity.

Each item in the feed includes:

Field Value
Title The topic title
Link The canonical URL of the topic
Publication date When the topic was published (in RFC 822 format)
Author The member's display name
Description A plain-text excerpt of the topic body (approximately 55 words)

Replies are not included in the feed. The feed covers top-level topics only.

Developer Notes

The feed respects the jetonomy_space_feed_posts filter, which lets developers modify the list of posts before the XML is rendered:

add_filter( 'jetonomy_space_feed_posts', function ( array $posts, object $space ): array {
    // Remove posts by specific authors, add extra items, etc.
    return $posts;
}, 10, 2 );

What's Next?

Learn about the Community Media page where site administrators can review and manage member uploads.

Community Media →

Search & Discovery

Find content with full-text search, tags, and filters.

Jetonomy's search finds content across your entire community in real time - topics, spaces, and tags - and lets you narrow results with powerful filters so members always land on exactly what they need.

What You Will Learn

  • How to run a full-text search from anywhere in the community
  • What the filter pills do and how to use them
  • How advanced filters refine results by date, author, tag, and sort order
  • How to show and hide the filter bar
  • How developers can extend search filters with a custom hook

The search bar sits in the community navigation, visible on every page. Type any keyword and press Enter or click the search icon. Jetonomy searches across:

  • Topic titles and content
  • Space names and descriptions
  • Tag names

Results appear on the search results page at /community/search/. Each result card shows the content type, the space it belongs to, the author, the date, and a short excerpt with your search term highlighted.

Search results page

Tip: Every word you type is required (AND), matched as a prefix - searching email digest finds posts containing both email... and digest.... Short words (under 4 characters) are ignored, so lead with the distinctive terms.

Filter Pills

At the top of the results page, four filter pills let you narrow by content type instantly:

Pill Shows
All Every matching result
Posts Topic results only
Spaces Space results only
Tags Tag results only

Click any pill to filter. The URL updates so you can share a filtered search link with your team.

Advanced Filters

Click the Filters disclosure to expand the advanced filter bar. It auto-expands whenever any filter is already active. These filters stack - you can combine them in any combination.

Search page with the Filters disclosure expanded, showing the Date from, Date to, Author, Tag, and Sort order controls

Date Range

Set a Date from and / or Date to date to restrict results to a window. Jetonomy filters by the post's original publish date, not its last reply date.

Author

Type a name or username to filter results to a specific author. Jetonomy resolves it to the matching member when you submit the search (there is no live typeahead). This is useful for reviewing a particular member's contributions or finding your own older posts.

Tag

Type a tag name to restrict results to posts that carry that tag. Combining author and tag filters is a fast way to find all posts by a specific member on a specific topic.

Sort Order

Option Orders by
Relevance Full-text match score (default)
Newest Most recently published first
Most Voted Highest net vote score first

Relevance is the default because it surfaces the best textual match. Switch to Newest when you know you are looking for a recent discussion. Switch to Most Voted when you want the community's highest-rated answer on a topic.

Collapsing the Filter Bar

Click Filters again to collapse the bar. Your active filters remain applied even while the bar is collapsed.

For Developers: Extending Search Filters

To modify the search query itself, use the jetonomy_search_query_args filter. It receives the assembled query arguments (q, space_id, date_from, date_to, author_id, tag_slug, sort) and must return the array.

add_filter( 'jetonomy_search_query_args', function( $args ) {
    // Restrict the query to a specific space.
    if ( ! empty( $_GET['space_id'] ) ) {
        $args['space_id'] = absint( $_GET['space_id'] );
    }
    return $args;
} );

To render extra controls in the filter bar, hook the jetonomy_search_filters action. It is a do_action (it returns nothing) fired just after the filter form, with three arguments: the query string $q, the active $filter pill, and an array of the current filter values.

add_action( 'jetonomy_search_filters', function( $q, $filter, $filters ) {
    // Echo your own markup for an extra filter control here.
}, 10, 3 );

See the Hooks Reference for the full parameter list.

What's Next?

Learn how tags work across spaces and how members can browse tag pages to find related content.

Tags →

  • SEO Pro - per-space meta, schema.org markup, OG/Twitter cards, and sitemaps so search engines surface your community.

Tags connect related discussions across your entire community. A member searching for help with "payments" can click the payments tag and instantly see every relevant topic - no matter which space it lives in.

Tag page listing all topics associated with a specific tag

What You Will Learn

  • How to add tags when creating a topic
  • How tag pills work on topic listings
  • How tag pages bring together content from multiple spaces
  • How to start a new discussion directly from a tag page
  • How the Popular Tags sidebar section helps members discover content
  • Why consistent tagging improves search and community quality

Adding Tags to a Topic

When creating or editing a topic, the Tags field appears below the content editor. Type one or more tag names separated by commas - for example python, django, architecture.

Reuse existing tag names whenever possible - reusing tags makes the tag page more useful for everyone who follows that topic, and matching an existing name (rather than a typo) keeps related posts together.

Tip: In Q&A spaces, good tags are the fastest route to an answer. A question tagged with "payments" and "stripe" will appear on both tag pages, doubling its chance of being seen by someone who can help.

Tag Pills on Topic Listings

Every topic card in a space listing shows its tags as small pills below the title. Clicking a tag pill takes you directly to that tag's page - you do not leave the flow to reach it.

Tags in listings are a fast way to browse by topic without running a search. If a space covers multiple subjects, tag pills help members navigate without scrolling through unrelated posts.

Tag Pages

Every tag has a dedicated page at /community/tag/tag-slug/. The tag page shows all published topics across all spaces that carry that tag, sorted by Latest (newest first) by default.

Members can switch the sort to Popular to find the best content on that topic - Popular orders by net vote score.

Tag pages are publicly accessible by default. If your community is private, tag pages respect the space access rules - topics in private spaces are not shown on tag pages to members who do not have access to those spaces.

Start a Discussion from a Tag Page

Logged-in members see a Start a discussion tagged [tag name] button in the tag page header. Clicking it opens the composer in the most active space for that tag with the tag already filled in - one click from browsing a topic to contributing to it.

The button only appears when the tag has at least one existing post, so it always points to a space where the conversation is already happening.

The Popular Tags section in the community sidebar lists the most frequently used tags across your community (up to 15), each linking to its tag page with a post count. It appears automatically - there is nothing to configure.

Developers can hide it with the jetonomy_show_sidebar_popular_tags filter:

add_filter( 'jetonomy_show_sidebar_popular_tags', '__return_false' );

To inject custom markup (an ad, a banner, a related-tags block) directly around the Popular Tags section, hook the jetonomy_sidebar_before_popular_tags or jetonomy_sidebar_after_popular_tags actions. Each receives the current space object, or null when the sidebar renders outside a space:

add_action( 'jetonomy_sidebar_after_popular_tags', function ( $space ) {
    // Echo your own markup below the Popular Tags list here.
} );

Headless and app consumers can read the same tag list over REST: GET /jetonomy/v1/tags returns tags ordered by popularity (or alphabetically with ?sort=alphabetical), with a limit of up to 100. See the Hooks Reference for the full parameter list.

How Tags Improve Your Community

Tags work across space boundaries. A tag named "onboarding" can tie together a tutorial in your Guides space, a question in your Q&A space, and a feature idea in your Ideas space. Members following that tag see the full picture regardless of which space each post lives in.

Encouraging consistent tag use - especially in high-traffic spaces - pays dividends in search quality. Jetonomy's full-text search treats tags as a search signal, so well-tagged posts surface higher in relevant queries.

Note: Tags are shared across all spaces. There is one global tag namespace. A tag created in your Support space appears on the same tag page as the same tag used in your Ideas space.

Admin Tag Management

Members create tags on the fly as they post, but admins get a dedicated management screen for the whole global tag namespace at Jetonomy → Tags in the WordPress admin. Managing tags here requires the jetonomy_manage_settings capability.

Jetonomy Tags admin screen: a table of tags with post counts, a bulk-action dropdown, a search box, and the Add New Tag form

The page lists every tag with its post count and gives you these controls:

Action What it does
Add New Tag Create a tag directly from the admin (name, optional slug) without having to post first
Edit Rename a tag or change its slug. The change applies everywhere the tag is used
Delete Remove a single tag. You can force-delete a tag even when posts are still attached to it
Bulk Delete Tick multiple tags, choose Delete from the bulk-action dropdown, and click Apply to clear several at once

The list also has a search box and a per-page picker, and it paginates so it stays fast even on communities with thousands of tags.

Use this screen to clean up duplicate or misspelled tags (for example merging "stripe" and "Stripe" by deleting the stray one), retire tags that are no longer relevant, or pre-create the canonical tags you want members to reuse. Because the namespace is global, an edit or delete here affects every space at once.

What's Next?

Learn how trending surfaces the discussions your community is talking about right now, and how to place a trending list anywhere on your site.

Trending →

Moderation & Trust

Keep your community safe with trust levels and moderation tools.

Jetonomy's trust system automatically promotes reliable members to higher privilege levels as they earn reputation - so you spend less time manually managing who can do what, and your most active members get recognized for their contributions.

Permissions settings with trust level thresholds and promotion rules

What You Will Learn

  • What the six trust levels are and what each one unlocks
  • How members earn reputation points
  • How automatic promotion works
  • How to adjust thresholds in your admin settings
  • How trust badges appear on member avatars

The Six Trust Levels

Jetonomy has six trust levels, numbered 0 through 5. Every new member starts at Trust Level 0. Levels 1 through 3 are earned automatically when a member meets the configured requirements. Levels 4 and 5 are not earned automatically - they are granted manually by an admin.

Promotion to levels 1-3 is based on a combination of activity stats, not a single reputation number. A member must meet every requirement listed for a level to reach it.

Level Name Default Requirements
TL0 Newcomer Automatic on signup
TL1 Member 5 posts, 3 days active, 10 replies received
TL2 Regular 30 posts, 20 days active, 50 reputation
TL3 Trusted 100 posts, 60 days active, 200 reputation
TL4 Leader Manually granted by an admin
TL5 Moderator Manually granted by an admin

What Each Level Unlocks

Trust levels expand what a member can do without moderator intervention.

Capability TL0 TL1 TL2 TL3 TL4 TL5
Create topics Yes Yes Yes Yes Yes Yes
Post replies Yes Yes Yes Yes Yes Yes
Flag content Yes Yes Yes Yes Yes Yes
Upload images No Yes Yes Yes Yes Yes
Edit / delete own posts No Yes Yes Yes Yes Yes
Daily post and rate limit lifted No Yes Yes Yes Yes Yes
Skip CAPTCHA No No Yes Yes Yes Yes
Create new spaces No No Yes Yes Yes Yes
Recategorize and rename others' topics No No No Yes Yes Yes
Moderate content and manage members No No No No Yes Yes
Manage settings, categories, and analytics No No No No No Yes

Each level adds to the one below it - a Trusted member (TL3) keeps everything TL1 and TL2 unlocked and gains the recategorize/rename abilities on top. Note that the higher levels are not just lifted limits: TL2 members can start their own spaces, TL3 members can tidy up the category and titles of any topic, and TL4/TL5 members effectively act as community staff (moderation, member management, and - at TL5 - settings, categories, and analytics).

Space moderators and WordPress admins always have full capabilities regardless of trust level.

Note: "Rate limit lifted" at TL1 refers to the per-day posting caps that apply to brand-new (TL0) accounts - 3 topics, 10 replies, and 5 votes per day by default. Those caps and how to tune them are covered in Anti-Spam Protection → Rate Limiting for New Members.

How Members Earn Reputation

Reputation is updated in real time whenever a qualifying event occurs.

Event Points
Your topic is upvoted +10
Your reply is upvoted +5
Your reply is accepted as an answer (Q&A) +15
Your idea is moved to Planned/Shipped +20
A flag you submitted is confirmed +5
Your topic or reply is downvoted -2
Your post is reported -10
A moderator deletes your content -20

Every point value is editable at Jetonomy → Settings → Permissions → Reputation Points - adjust any action's score without touching code.

Reputation points accumulate on your public profile. The leaderboard ranks members by reputation score - see the Leaderboard doc for details.

Automatic Promotion

A cron job runs twice daily to evaluate all members against the current requirements for levels 1-3. Any member who meets every requirement for the next level is automatically promoted.

Promotion is silent - members are not notified by default. You can add a welcome notification using the jetonomy_trust_level_changed action hook if you want to acknowledge promotions.

Demotion works the same way. If a member's reputation falls below a threshold (for example, because posts were deleted), they are automatically moved back to the appropriate level on the next cron run.

Tip: You can set a member's trust level directly from Jetonomy → Users in the WordPress admin. Find the user and click Change Trust Level, then pick the level. This is a manual override that sets the level immediately - useful for elevating a known expert or correcting an edge case.

Jetonomy Users admin page listing community members with per-row Change Trust Level and Ban / Unban controls

The Jetonomy → Users page is the central place to manage individual members: each row shows the member's trust level, post and reputation stats, and per-row controls to Change Trust Level and to Ban / Unban (covered in Banning Members).

Configuring Thresholds

Go to Jetonomy → Settings → Permissions to adjust the promotion requirements. Each of the three earned levels (1, 2, and 3) has its own row with four inputs: posts, days active, reputation, and replies received. A member must meet every value in a row to reach that level. Changes take effect on the next cron run.

Levels 4 and 5 have no requirement inputs - they are granted manually from Jetonomy → Users and cannot be earned automatically.

Lower requirements make promotion faster and more accessible. Higher requirements make higher trust levels a meaningful achievement. There is no right answer - tune these to the pace and size of your community.

Note: Setting a requirement to 0 removes it as a gate for that level. For example, setting a level's reputation requirement to 0 means members can reach it on activity stats alone.

Trust Badges on Avatars

Each trust level has a colored badge that appears on a member's avatar across topic listings, reply cards, and their profile page. The badge uses the data-jt-tl attribute so you can restyle it in your theme using CSS if needed.

Level Badge Color
TL0 Grey
TL1 Blue
TL2 Green
TL3 Teal
TL4 Purple
TL5 Gold

Why Trust-Based Moderation Beats Manual Role Assignment

In a traditional forum, you manually decide who is a "trusted" member. That does not scale. With Jetonomy's trust system, your community self-selects. Members who contribute quality content earn their way to higher levels automatically. You only need to intervene in edge cases - banning bad actors or manually elevating a known expert to a higher level.

For Developers

Trust and moderation events fire WordPress action hooks you can listen to in a small custom plugin or your theme's functions.php - for example to send a welcome message when a member is promoted, or to log every resolved flag to an external system:

  • jetonomy_trust_level_changed - fires when a member's trust level goes up or down.
  • jetonomy_flag_resolved - fires when a moderator marks a flag valid or dismisses it.
  • jetonomy_sidebar_auth_card - lets you add content to the community sidebar.

The full signatures (parameters and when each one runs) are in the Hooks Reference in the developer guide.

What's Next?

Learn how members can flag content for review and how flagged content reaches the moderation queue.

Flagging & Reporting Content →

Flagging lets any logged-in member report content that breaks your community rules. It is the first step in the moderation pipeline - members surface problems, and your moderators review and act.

Admin moderation dashboard showing flagged content awaiting review

What You Will Learn

  • How to flag a topic or reply
  • How to report an entire member account
  • How to block a member from the mobile app or the REST API
  • What information a flag captures
  • What the reason categories mean and where they come from
  • Who can flag content and what the restrictions are
  • What happens to flagged content before a moderator reviews it
  • How flags flow into the moderation queue

How to Flag Content

Every topic and every reply has a ... (more actions) menu. Open it and click Report. A prompt dialog appears asking why you are reporting the content. Type a brief description of the problem - for example, "This contains spam links" or "This is abusive toward another member" - and confirm.

Member reporting a post: the ... menu Report action and the "Why are you reporting this post?" reason prompt

The flag is saved immediately and you receive a confirmation message. The text you type is stored as the flag's description; the member-side report files every flag under the Other reason category, and moderators see that category alongside your description when they review it.

Tip: A good flag reason helps moderators act faster. "Spam" alone works, but "Contains a link to a commercial site unrelated to this community" gives the moderator everything they need without having to investigate.

Who Can Flag Content

Any logged-in member can flag a topic or reply, regardless of their trust level. There is one restriction: you cannot flag your own content.

The flag button is not visible to guests (logged-out visitors). If you want guests to be able to report content, you will need a custom solution - the built-in flagging system requires authentication.

There is no daily limit on flags per member. A member who finds multiple pieces of problematic content can flag all of them.

Reporting a Member

Flagging is not limited to individual posts and replies - a member can also report an entire user account. This is for cases where the problem is the person rather than one piece of content (for example, a member sending unwanted messages or repeatedly causing trouble across many topics).

To report a member, open their profile page (/community/u/their-username/) and click the Report button next to the Message button. A prompt asks why you are reporting the user; type a brief reason and confirm. As with content flags, you cannot report your own account, and the control only appears to logged-in members.

A user report creates a flag the same way a content report does - it is recorded against that member's account, captures your reason text and your name as the reporter, and surfaces in the moderation tools for a moderator to review and act on (for example, by banning the member if the report is justified).

Blocking a Member (1.8.0+)

Blocking hides a member's replies from your own view without removing them for anyone else - useful when you want to stop seeing someone's content without waiting on a moderator. As of 1.8.0, blocking is available from the mobile app and the REST API.

You can report the member in the same action - blocking and filing a report travel together in one request, so members do not need to submit them separately.

If the blocked member's reply has other replies nested underneath it, only their own content is hidden; the innocent replies in that nested thread stay visible.

Note: Blocking is a personal, per-viewer preference. It does not stop the blocked member from posting, and moderators and other members still see everything as normal. To remove someone's ability to post at all, a moderator needs to ban them.

What a Flag Captures

When a flag is submitted, Jetonomy records:

  • The content or member being flagged (a post, a reply, or a user account, with its ID)
  • The member who submitted the flag
  • The reason category and the reason text they entered
  • The timestamp

Moderators see all of this information when they review the flag in the moderation queue.

Reason Categories vs Free Text

When a moderator opens the queue, each flag shows a reason category - one of Spam, Offensive, Off-topic, Harassment, or Other. The built-in Report button that members click always files under the Other category and stores their typed description, so most member-submitted flags you see will be tagged Other with a free-text note.

The other categories (Spam, Offensive, Off-topic, Harassment) come from automated and advanced paths - the REST API, WP-CLI, and Jetonomy Pro's moderation automation - which can file a flag with a specific structured reason. So if you see category chips other than Other in your queue, those flags were raised by an integration or automation rule, not chosen by a member in the standard report dialog.

What Happens to Flagged Content

Flagging alone does not change the visibility of a post or reply. The content stays live and readable by all members until a moderator reviews it and takes action. This is intentional - hiding content automatically on a single flag would allow abuse of the flagging system.

A moderator can then mark the flag Valid (confirming the content breaks the rules, which trashes it) or Dismiss it (marking it unfounded, leaving the content live). See the Moderation Queue guide for the full review workflow.

Note: If the same piece of content receives multiple flags from different members, all flags are grouped under that content item in the moderation queue. Moderators see the total flag count and each individual reason.

Preventing Flag Abuse

Jetonomy does not currently auto-penalize members who submit flags that moderators consistently dismiss. If you have a member who is abusing the flagging system, handle it by adjusting their trust level or banning them from Jetonomy → Users in the WordPress admin.

What's Next?

See how flagged content and posts pending approval appear in the moderation queue, and learn how moderators take action.

Moderation Queue →

The moderation queue is your single dashboard for everything that needs human review - posts waiting for approval, flagged content, and items caught by spam filters. You can action everything from one page without digging through individual topics.

Admin moderation queue with pending items and per-row action controls

What You Will Learn

  • How to access the moderation queue
  • What types of content appear in the queue
  • What actions you can take on each item
  • How per-space moderation differs from global moderation
  • How Akismet-held content appears in the queue
  • Where to ban a member when content-level actions are not enough

Accessing the Moderation Queue

Go to Jetonomy → Moderation in your WordPress admin. The page is also accessible from the frontend at /community/mod/ - WordPress Administrators and users with the jetonomy_moderate capability can access both routes.

The queue shows a count badge on the admin menu item whenever there are items waiting for review.

What Appears in the Queue

The queue is split into four tabbed views, each with a count of how many items it holds:

Pending Posts

These are topics submitted in a space with Require Post Approval enabled. They are not visible to other community members until a moderator approves them.

Each pending item shows the full content, the author, the space it was submitted to, and how long it has been waiting. Items are ordered oldest first so nothing sits in the queue unnoticed.

Automated checks can also route content here. Jetonomy Pro's AI spam detection and moderation rules can place a post or reply into this held "pending" state for review rather than publishing it outright.

Pending Replies

The same as Pending Posts, but for replies awaiting approval in spaces that require it. Replies get their own tab so you can clear topics and replies independently.

Flags

These are live topics and replies that members have flagged for review. Flagged content stays visible in the community until a moderator acts. Each item shows the content, the flag reason(s), how many unique members flagged it, and the timestamp of the most recent flag.

Banned Users

A list of members who are currently banned, with the option to lift each ban. See Banning Members for the full ban workflow.

Available Actions

Actions differ between the pending tabs and the Flags tab.

On Pending Posts and Pending Replies, each item has three buttons:

Action What it does
Approve Publishes the pending post or reply so the community can see it
Spam Marks the content as spam and moves it to trash; updates Akismet's spam training if Akismet is active
Trash Moves the content to trash without marking it as spam

On the Flags tab, each flag row has two buttons:

Action What it does
Valid (Trash) Confirms the flag was justified - the content is trashed and the flag resolved
Dismiss Marks the flag unfounded - the content stays live and the flag is resolved

Tip: Use Spam rather than Trash when content is clearly commercial spam. This trains Akismet for your site, making future auto-detection more accurate.

Spam and Trash ask for confirmation. Both actions remove content, so clicking either one opens a confirmation dialog before anything is deleted - individually per row and for the bulk action across selected rows. Approve does not prompt, since it only publishes. This guards against a misclick trashing a legitimate post.

The flag counter updates live. When you resolve a flag (Valid or Dismiss), the resolved row is removed and the "N pending" flag badge decrements immediately, without a page reload. When the last flag is cleared, the badge disappears. You always see the true outstanding count without refreshing.

Per-Space vs Global Moderation

The queue shows content from all spaces by default. Use the Space filter dropdown at the top of the queue to narrow to a single space. This is useful when you have dedicated space moderators - a moderator for your Support space only needs to see Support space items.

Space moderators who do not have global admin access see only their own spaces' items when they visit /community/mod/. They do not see content from spaces they do not moderate.

Frontend moderation dashboard at /community/mod/ as a space moderator sees it, scoped to their own spaces

Fixed in 1.4.1: moderators of multiple spaces now see every queue they own when they visit /community/mod/. Earlier versions could redirect a multi-space moderator away from the dashboard if access checks ran in the wrong order. If you have moderators who report "I can see one space's queue but not the others," update to 1.4.1 and the dashboard will load all of them.

Akismet Integration

If the Akismet Anti-Spam plugin is active and configured on your site, Jetonomy automatically passes new posts and replies through Akismet before saving them. If Akismet marks content as spam:

  • The post or reply is saved with a Spam status (not Pending)
  • It does not appear in the community

Spam-flagged content is set to a Spam status rather than surfaced as a dedicated tab in the moderation screen. The four moderation tabs are Pending Posts, Pending Replies, Flags, and Banned Users.

Note: Akismet integration requires the Akismet plugin to be installed, activated, and connected with a valid API key. Jetonomy does not bundle Akismet - it integrates with it automatically when present.

Banning Members

Approve, Mark as Spam, and Trash all act on individual pieces of content. When a member is repeatedly disruptive, content-level actions are not enough and you need to act on the person instead. The Banned Users tab here shows everyone who is currently banned, with an Unban control on each row.

Banning is a subsystem of its own - three ban types, durations, auto-expiry, and the Jetonomy → Users admin page - covered in full in its own guide.

Banning Members →

What's Next?

Learn about Jetonomy's built-in anti-spam tools - reCAPTCHA, Turnstile, and rate limiting - that reduce how much reaches the moderation queue in the first place.

Anti-Spam Protection →

Spam is the fastest way to kill a community's quality. Jetonomy has multiple layers of protection that work silently in the background - your real members never know they are there.

Anti-spam settings with CAPTCHA provider selection and API key fields

What You Will Learn

  • How to enable reCAPTCHA v3 or Cloudflare Turnstile
  • Where to enter your API keys in the admin settings
  • How trusted members are automatically exempted from CAPTCHA
  • How Akismet integration catches spam that passes CAPTCHA
  • How rate limiting protects against new-member spam floods

Choosing a CAPTCHA Provider

Go to Jetonomy → Settings → Anti-Spam to configure your CAPTCHA provider.

Jetonomy supports two providers:

Provider Type User friction
Google reCAPTCHA v3 Score-based, invisible None - runs in background
Cloudflare Turnstile Challenge-based, invisible None - usually auto-passes

Both are invisible to real users. reCAPTCHA v3 assigns a bot-likelihood score and blocks low-scoring submissions. Cloudflare Turnstile analyzes browser signals and shows a challenge only when it cannot verify the user automatically.

Choose Cloudflare Turnstile if your community has members who are privacy-conscious or who block Google services. Both providers have free tiers sufficient for most communities.

Setting Up reCAPTCHA v3

  1. Go to google.com/recaptcha and create a v3 site.
  2. Add your domain to the allowed domains list.
  3. Copy the Site Key and Secret Key.
  4. In Jetonomy → Settings → Anti-Spam, select reCAPTCHA v3, paste both keys, and click Save.

Setting Up Cloudflare Turnstile

  1. Log in to your Cloudflare dashboard and go to Turnstile.
  2. Add a site and set the widget mode to Invisible.
  3. Copy the Site Key and Secret Key.
  4. In Jetonomy → Settings → Anti-Spam, select Cloudflare Turnstile, paste both keys, and click Save.

Note: After saving, Jetonomy automatically loads the CAPTCHA script on the post and reply forms. You do not need to add any code to your theme.

Trusted Members Skip CAPTCHA

Members at Trust Level 2 and above never see or trigger a CAPTCHA check. Jetonomy bypasses the verification entirely for them - the API call is never made.

This means your most active, trusted members post without any friction while new accounts get verified. As members earn reputation and cross the TL2 threshold, they transition out of CAPTCHA checks automatically.

Akismet Integration

If the Akismet plugin is active on your site, Jetonomy sends every new post and reply through Akismet's spam detection API as a second layer of protection. Content that passes CAPTCHA but that Akismet marks as spam is held in the moderation queue rather than published.

See the Moderation Queue guide for how to review Akismet-held content.

Akismet and CAPTCHA work independently. You can run both at the same time for maximum protection, or use either one alone.

AI-Powered Spam Detection (Pro)

Jetonomy Pro's AI extension adds a third layer: posts and replies publish instantly, then a background reviewer scores each one for spam probability on a schedule (the AI is never in the posting path). Posts above the configured threshold are moved to the moderation queue with a reason the model generated. Posts below it stay published as normal.

This catches the kinds of spam that pattern matching and Akismet miss - subtly rewritten affiliate spam, contextually wrong replies designed to pad posting history, and coordinated attacks that use clean accounts.

Run the detection through any supported provider - OpenAI, Anthropic, a custom endpoint, or self-hosted Ollama (which keeps all content on your own server). See AI Integration for setup.

AI spam detection stacks on top of CAPTCHA, Akismet, and rate limits - not instead of them. Each layer catches different threats.

Rate Limiting for New Members

Trust Level 0 members (brand-new accounts) are subject to posting rate limits regardless of CAPTCHA:

Content type Default limit
Topics per day 3
Replies per day 10
Votes per day 5

Limits reset after 24 hours. Members at Trust Level 1 and above are exempt from all rate limits.

You can adjust the default thresholds at Jetonomy → Settings → Permissions. Setting a limit to 0 disables it for that trust level.

Tip: Rate limiting is your best defense against coordinated spam from many new accounts. Even if a bot farm passes CAPTCHA, each account can only post 3 topics before hitting the daily limit.

Rate Limit Settings

These rate limits live alongside the trust system. The per-day caps shown above are the Trust Level 0 defaults; every trust level above TL0 is exempt. See Trust Levels for how a member graduates out of rate limiting by earning their way to TL1, and where the "Rate limit lifted" capability fits in the wider trust ladder.

What's Next?

When spam prevention is not enough and you need to act on a specific member, the next guide covers banning.

Banning Members →

Approving, marking as spam, and trashing all act on a single piece of content. Banning acts on the person. When a member is not just posting one bad item but is repeatedly disruptive - or is a confirmed bad actor - you ban the account so they cannot keep posting. This guide covers the three ban types, how long a ban lasts, and where to manage bans.

Jetonomy Users admin page listing community members with per-row Ban / Unban and Change Trust Level controls

What You Will Learn

  • Where to ban and unban members
  • The three ban types and when to use each
  • How ban durations work, including automatic expiry
  • How to lift a ban early
  • Who is allowed to ban members

Where to Manage Bans

You ban and unban members from the Jetonomy → Users page in the WordPress admin. The page lists every community member with their trust level and stats, and gives each row a Ban / Unban control (plus a quick Silence link). Currently banned members also appear on the Banned Users tab of the Moderation Queue, where you can lift each ban.

Banning requires the jetonomy_moderate capability - the same capability that gates the moderation queue itself. WordPress administrators and Trust Level 4+ members have it by default.

The Ban Dialog

Clicking Ban on a member's row opens the Ban User dialog with three choices:

Ban Types

Jetonomy supports three levels of restriction so the response fits the situation:

Type Effect
Global Ban The member is blocked from posting anywhere in the community
Space Ban The member is blocked from a single space but can still participate elsewhere
Silence The member can still read everything but cannot post or reply anywhere

Tip: The Silence quick-link on each user row opens this same dialog pre-set to the Silence type with a 7-day duration - a fast way to cool down a heated member without a full ban.

Duration

Pick how long the ban lasts from the Duration selector:

Duration Effect
Permanent The ban stays in place until you lift it manually
1 Day Lifts automatically 1 day after it is applied
7 Days Lifts automatically 7 days after it is applied
30 Days Lifts automatically 30 days after it is applied

Time-limited bans lift themselves automatically when they expire, so you do not have to remember to unban a member after a cooling-off period.

Reason

The dialog has an optional Reason field. The reason is stored with the ban for your own records - it does not change how the ban behaves. Recording why you banned someone makes it easy for you or another moderator to understand the history later.

Bans Apply to the API and the App Too

A ban is not just a front-end block. Jetonomy checks account status on every REST write - creating posts and replies, voting, registering a push device, and every other mutation - so a banned member is rejected no matter how they connect. That includes Application Password and mobile-app sessions, which authenticate outside the normal WordPress login flow. A banned account cannot post around the ban by switching to the app or calling the API directly; the write is refused with a "banned" error before anything is saved.

The same gate also stops members who have not confirmed their email (pending verification) from writing through the API until they finish signing up.

Lifting a Ban Early

To remove a ban before it expires, click Unban on the member's row (on the Users page or the Banned Users tab). The restriction is cleared and the member can participate again immediately.

When to Ban vs. Act on Content

Reach for a ban only when content-level actions are not enough:

  • A single bad post - use Trash or Mark as Spam from the Moderation Queue.
  • A member causing trouble in one space - use a Space Ban so they keep their access elsewhere.
  • A member you want to cool down, not remove - use Silence (they can still read).
  • A member who keeps coming back - use a Global Ban, permanent or timed, to stop the behavior at the source.

What's Next?

Learn how in-app notifications keep your members engaged and informed about replies, mentions, and votes.

Notifications →

Notifications & Email

Stay informed with in-app and email notifications.

Notifications keep your community members in the loop without requiring them to check back manually. Every relevant activity (replies, mentions, votes) surfaces instantly in the notification bell so members always know when something needs their attention.

Notifications panel showing recent activity alerts and unread count

What You Will Learn

  • How the notification bell works and where it appears
  • Every notification type and when each one fires
  • How to mark notifications as read
  • How to view, filter, and delete notifications on the full notifications page
  • How the verification reminder email nudges members who never confirmed their address
  • Where members set their personal notification preferences

The Notification Bell

The notification bell icon appears in the community navigation bar on every page. When you have unread notifications, a red badge shows the count. The count updates automatically. You do not need to refresh the page.

Click the bell to open a dropdown showing your most recent notifications. The dropdown lazy-loads its content when you click, keeping page load times fast.

Each notification in the dropdown shows:

  • The notification type (icon)
  • A summary of what happened (for example, "Sarah replied to your topic")
  • The time it occurred
  • A direct link to the relevant content

Clicking a notification in the dropdown marks it as read and navigates you to the relevant topic, reply, or profile.

Notification Types

Jetonomy fires a notification for each of the following events:

Jetonomy ships with ten notification types. The Channels column shows the default channels for each type; "In-app" means it appears in the bell, "email" means it also sends an email by default. Every type can be turned on or off per channel, both site-wide by an admin and per-member. See the Preferences section below.

Event Who receives it Channels (default)
Someone replies to your topic Topic author In-app, email
Someone replies to your reply (threaded) Reply author In-app
Someone mentions you with @username Mentioned member In-app, email
Your reply is accepted as an answer (Q&A) Reply author In-app, email
An idea's status changes (Ideas spaces) Idea author In-app, email
A new topic is posted in a space you follow All followers of that space In-app
You earn a badge (Jetonomy Pro) The member In-app
Your topic or reply receives a vote Content author In-app
A moderator reviews or acts on your content Content author In-app, email
A member requests to join a space you manage Space managers In-app, email

Note: The badge notification only fires on communities running Jetonomy Pro. Votes are reported through a single "vote on your post" notification (it covers both upvotes and downvotes).

These channels are the defaults that ship with Jetonomy. An administrator can change the site-wide default for any type under Jetonomy → Settings → Email, and each member can override their own preferences. The full default table lives in Email Notifications.

Marking Notifications as Read

Mark one as read: Click any notification in the dropdown. Navigating to it marks it read automatically.

Mark all as read: Click the Mark all as read link at the top of the dropdown. All notifications are cleared in a single action. The badge disappears immediately.

Unread notifications are highlighted with a subtle background tint in the dropdown so you can spot them at a glance.

The Full Notifications Page

The dropdown shows your most recent notifications, roughly the last 10 to 20 items depending on screen size. To see your full notification history, click See all notifications at the bottom of the dropdown or navigate directly to /community/notifications/.

The full page lists every notification you have received, loading 20 at a time with a Load More button at the bottom. A weekly background job marks unread notifications older than 30 days as read. Notifications are not auto-deleted from the database.

Full notifications page showing the All / Unread / Mentions / Replies / Votes / Badges filter tabs with count badges, and the bulk-select toolbar with Mark read and Delete buttons above selected rows

Filter Tabs

The page has a row of filter tabs across the top so members can focus on one kind of update at a time. Each tab shows a count badge of how many notifications match it, so you can see at a glance where the activity is.

Tab What it shows
All Every notification you have received
Unread Only notifications you have not opened yet
Mentions Notifications where someone @-mentioned you
Replies Replies to your posts and to your replies
Votes Upvotes and downvotes on your posts and replies
Badges Badges you have earned (only shown when Jetonomy Pro is active)

Switching tabs reloads the list filtered to that type and keeps your place. The Badges tab appears only on communities running Jetonomy Pro, since badges are a Pro feature.

Deleting Notifications

Members can clear out notifications they no longer need, not just mark them read.

  • Delete one: Open the ... actions menu on any notification row and choose Delete. The row is removed immediately.
  • Bulk delete: Tick the checkbox on one or more rows. A toolbar appears at the top of the list showing how many you have selected, with Mark read and Delete buttons that apply to every selected row at once. A Select all on page checkbox selects the whole visible page in one tick.

Deleting is permanent for that member's own inbox. It does not affect anyone else's notifications or the underlying post or reply.

Note: Per-notification delete and the bulk-delete toolbar were added in 1.4.3.

The Verification Reminder Email

When a community requires email verification at signup (Jetonomy → Settings → Email), some members register but never click the verification link in their welcome email. Jetonomy sends those members a single follow-up reminder email nudging them to finish verifying, so they do not get stuck unable to participate.

  • The reminder is sent once per member. It will never email the same person twice.
  • It goes out a configurable number of hours after registration. Set the delay in jetonomy_settings.verification_reminder_hours. Setting it to 0 disables the reminder entirely.
  • It respects each member's email opt-out, so anyone who has opted out of community email is skipped.
  • It uses the same branded email template as your other Jetonomy emails, so the subject and body can be customised under Settings → Email → Email Templates.
  • The reminder only runs while email verification is switched on. Turn verification off and there is nothing to remind about.

Per-User Notification Preferences

Each member can control which notification types they receive. Go to Profile → Edit Profile → Notifications (at /community/u/your-username/edit/).

Edit Profile Notifications tab showing the per-type Web and Email toggle grid with the Pause all email notifications switch below it

Options are:

  • In-app notifications on/off per type
  • Email notifications on/off per type (see Email Notifications for the full email guide)
  • A single Pause all email notifications toggle at the bottom of the same Notifications tab. Turning it on snoozes every email type at once while still showing web notifications in the community, so a member can step away from email without losing their per-type settings. Turning it back off restores their previous choices.

Members cannot disable notifications for direct mentions. The @mention notification is always delivered to ensure important communications reach their target.

Note: Administrators can set the default notification preferences that new members start with. Go to Jetonomy → Settings → Email to configure the defaults applied at signup.

What's Next?

Learn how to configure email notification delivery, set community-wide defaults, and understand which notification types support email.

Email Notifications →

Pro adds more ways to reach and message members:

Email notifications bring members back to your community even when they are not actively browsing. Jetonomy sends a notification email for every in-app event type, and both you and your members have control over which emails are delivered.

Admin email settings with notification toggles and sender configuration

What You Will Learn

  • Which notification types send email
  • How to configure default email settings for new members
  • How members control their own email preferences
  • How one-click unsubscribe links work
  • How Jetonomy sends email and how to use your own SMTP provider
  • What email digest options are available in Jetonomy Pro

Which Notification Types Send Email

Every notification type that appears in the in-app bell can also send an email. The email mirrors the in-app notification - it names the event, shows a short excerpt of the content, and includes a direct link back to the relevant topic or reply.

Jetonomy ships with ten notification types. Each one sends email by default, or not, as shown below:

Notification type Email sent by default
Reply to your topic Yes
Reply to your reply No
@mention Yes
Answer accepted (Q&A) Yes
Idea roadmap status changed Yes
New post in followed space No
Badge earned (Jetonomy Pro) No
Vote on your post No
Moderator action on your content Yes
Space join request Yes

The defaults above are what Jetonomy applies when a new member signs up. Administrators set these site-wide defaults in Jetonomy → Settings → Email; the full settings reference, including the matching web (in-app) defaults, lives in Email Settings. Each member can then override any type for themselves (see "How Members Control Their Preferences" below).

Tip: "New post in followed space", vote, and badge emails are off by default because they can occur frequently. A member who follows many active spaces would otherwise receive a high volume of email. Let members opt in rather than having to opt out.

Editable Email Templates (updated in 1.4.1)

Every notification email Jetonomy sends has an editable template - subject and body - at Jetonomy → Settings → Email → Templates. Change the wording to match your community's voice without writing any code.

Two improvements landed in 1.4.1 that are worth knowing about:

  • Reset to default button on every template - one click restores the shipped subject and body. No more retyping if you change your mind or want to start over.
  • Verification reminder template is now editable from the same screen. The reminder fires once per member, a configurable number of hours after sign-up (default 24), if they haven't clicked the verification link in their welcome email. Set the delay in jetonomy_settings.verification_reminder_hours, or set it to 0 to disable the reminder entirely. See the verification reminder section for the full behaviour.

Defaults now have a single source of truth so reset always restores the exact copy the plugin ships with - even if a future update changes the default wording, your reset still gets the version you'd see on a fresh install.

Template Placeholders

You can use these placeholders in any subject or body. They are replaced with the real values when the email is sent:

Placeholder Replaced with
{site} Your site name
{user} The recipient's display name
{message} The notification summary text
{type} The notification type key
{url} A direct link to the relevant content
{post_title} The title of the related topic or idea
{actor_display_name} The member who triggered the notification
{reply_excerpt} A short excerpt of the reply
{space_title} The space the activity happened in

Any placeholder with no value for a given event renders as an empty string, so it never shows up as literal {post_title} text in the email.

Configuring Default Settings

The community-wide defaults - which types email by default, the From name, the From address, email branding, and the editable templates - all live on the admin Jetonomy → Settings → Email screen. That screen is documented in full, with every setting key, default, and location, in Email Settings.

Changes to defaults apply only to new members who sign up after the change. Existing members keep their current preferences.

How Members Control Their Preferences

Each member can override the defaults from their profile settings at /community/u/their-username/edit/ under the Notifications tab. Every notification type has a separate toggle for in-app and email delivery.

Members can disable all notification emails at once with the Pause all email toggle. This is the equivalent of a temporary snooze - all preferences are preserved so they can turn email back on later.

One-Click Unsubscribe

Every notification email sent by Jetonomy includes an unsubscribe link in the footer. Clicking it immediately unsubscribes the member from that one notification type and shows a confirmation message.

No login is required to unsubscribe - the link contains a signed token that authenticates the action. This keeps unsubscribe rates low and means members who receive an email do not need to remember their password to opt out.

How Jetonomy Sends Email

Jetonomy uses WordPress's wp_mail() function to send all notification emails. This means it automatically works with any SMTP plugin you have installed - WP Mail SMTP, FluentSMTP, Postmark, Mailgun, or any other wp_mail compatible provider.

No additional configuration is needed in Jetonomy when you add an SMTP plugin - it just works.

Note: If you send high volumes of notification email, use a dedicated transactional email provider (Postmark, Mailgun, Sendgrid) rather than your hosting server's mail. Dedicated providers give you delivery tracking, bounce handling, and higher sending limits.

Email Digest (Jetonomy Pro)

PRO - This feature requires Jetonomy Pro.

The Email Digest feature bundles all notifications from a configurable time window into a single summary email. Members can choose daily or weekly digests instead of receiving individual emails for each event. This dramatically reduces email volume for active community members while keeping them informed.

What's Next?

Learn how member profiles work, what stats are displayed, and how members can edit their own information.

User Profiles →

User Profiles & Leaderboard

Member profiles, reputation, and community rankings.

Every member in your community has a public profile page that shows who they are, how much they contribute, and what they have been up to. Profiles build trust between members and give contributors the recognition they have earned.

What You Will Learn

  • What a member profile page shows
  • How online status works on profiles
  • Which tabs are available and who can see each one
  • How members edit their own profile
  • How members delete their own account from the app or API
  • Where reputation and trust level appear

The Profile Page

Every member has a profile page at /community/u/their-username/. Anyone can visit this page - no login is required unless your community is set to private.

User profile page

Header Section

The profile header shows:

  • Avatar - pulled from WordPress Gravatar by default. Members can upload a custom photo from the Edit Profile page and crop it square before it is saved.
  • Display name - the name shown across all community activity
  • Username - the login handle used in @mentions and the profile URL
  • Bio - a short text description the member writes themselves
  • Join date - when the member's account was created
  • Online status - a green dot appears if the member was active in the last 5 minutes (see Online Status)

Stats Bar

Below the header, four stats appear at a glance:

Stat What it shows
Reputation Total reputation score earned
Posts Number of published topics
Replies Number of published replies
Trust Level Current trust level badge and name

The trust level badge uses the same color coding as it does on reply cards - grey for TL0, scaling up to gold for TL5.

Profile Tabs

Posts

Lists every topic the member has published, paginated 20 per page, sorted newest first. Each item shows the topic title, the space it belongs to, the vote score, and the reply count.

Replies

Lists every reply the member has posted across all spaces, paginated 20 per page, newest first. Each item shows the reply excerpt, the topic it belongs to, and the vote score.

Votes

Shows the content this member has voted on. The tab is visible to the member themselves and to WordPress Administrators. Other members cannot see this tab - voting is semi-private.

Bookmarks

Lists the topics this member has bookmarked, at /community/u/their-username/bookmarks/. Like Drafts, this tab appears only on the member's own profile. Bookmarks are private to the member who saved them, so no one else (including administrators viewing the public profile) sees this tab. It gives members a quick personal reading list of posts they wanted to come back to. See Bookmarks & Following for how members save and manage bookmarks.

Drafts

Shows unpublished draft topics saved by this member. This tab is visible only to the profile owner and WordPress Administrators. Drafts are never exposed publicly.

Tip: Encourage active members to complete their bio. Profiles with a bio and a recognizable avatar get more replies - other members feel more comfortable engaging when they know who they are talking to.

Editing a Profile

Members can edit their own profile at /community/u/their-username/edit/. This page is accessible from the Edit Profile button on the profile page header.

Edit Profile page with display name, bio, avatar upload, and notification preference fields

Editable fields:

  • Display name
  • Bio
  • Avatar - upload a JPG, PNG, GIF, or WebP. After choosing a non-GIF image, a crop dialog opens: drag to reposition, use the slider to zoom, then Crop and upload saves a square avatar (animated GIFs upload as-is to preserve animation). The "Remove photo" button reverts to the Gravatar.
  • Notification preferences (email and in-app toggles per type)

Avatar crop dialog open with the image preview, zoom slider, and Crop and upload button

WordPress Administrators can edit any member's profile from the standard WordPress Users admin as well.

Deleting Your Account (1.8.0+)

Members can permanently delete their own account from the mobile app or the REST API (DELETE /users/me). There is no web front-end button for this yet - it is available through the app and the API only.

Account deletion is permanent and cannot be undone from the community front end - make sure your app or client confirms with the member before sending the request.

Embedding Profiles on a Page

You do not have to send members to the full profile page to surface profile information. Two shortcodes let you drop a compact member card or a space member roster onto any WordPress page, post, or widget area. Both are shortcode-only - there is no matching Gutenberg block, so add them with a Shortcode block or directly in the editor.

Member profile card

[jetonomy_user_profile user_id="0"]

Renders a small card with the member's display name, trust level badge, a short bio excerpt, and their reputation and post count. Set user_id to a specific member's ID, or leave it as 0 (the default) to show the currently logged-in member's card - handy for a "your profile at a glance" panel on a members-only page.

Space member roster

[jetonomy_space_members space_id="5" count="20"]

Lists the members of one space, ranked by reputation. Drop it on a space landing page or an "About this community" page so visitors can see who is active.

Attribute Default What it does
space_id 0 The ID of the space whose members to list. Required - leave it unset and nothing renders.
count 10 How many members to show, highest reputation first.

Note: The roster attribute is count, not limit.

What's Next?

See how your top contributors rank against each other on the community leaderboard.

Leaderboard →

  • Custom Badges - award badges automatically or by hand to recognize members.

The leaderboard turns quality participation into something visible and worth competing for. Your top contributors are recognized publicly, which encourages every member to engage more thoughtfully.

What You Will Learn

  • How to find the leaderboard page
  • What information is displayed for each member
  • How the top 3 medal positions work
  • How to add the leaderboard sidebar widget
  • Why public recognition improves community quality

The Leaderboard Page

The community leaderboard is available at /community/leaderboard/. Any member - and guests, if your community is public - can view it. No login is required.

Leaderboard page

Members are ranked by total reputation score, highest first. The leaderboard updates in real time as reputation changes - there is no daily cache delay between earning reputation and appearing in the rankings.

How Reputation Is Earned

Reputation is what drives a member's position on the board. Members earn it through quality participation - upvotes on their topics and replies, and having an answer accepted on a question. Downvotes and moderator removals subtract from it. The exact point values are defaults you can adjust under Jetonomy → Settings, so you can tune what your community rewards. See Voting & Reputation for the full list of actions and their default point values.

Time Period Filter

The leaderboard page has a row of period pills at the top that scope which members appear:

Pill What it shows
All time Every member, ranked by reputation (the default)
This month Only members active within the last 30 days, ranked by reputation
This week Only members active within the last 7 days, ranked by reputation

The month and week filters narrow the board to recently active members - ranking is still by total reputation score within each window, not by reputation earned during that period.

What Each Row Shows

Every row on the leaderboard displays:

Column Description
Rank Position number (1, 2, 3...)
Avatar Member's initials avatar - their first two initials, not the uploaded photo, so the board stays fast (no online status dot on the leaderboard)
Name Display name, linked to their profile page
Reputation Total reputation score
Posts Total published topic count

Clicking a member's name goes directly to their profile page.

Top 3 Medal Positions

The first three positions on the leaderboard display a medal icon next to the rank number:

Position Medal
1st place Gold medal
2nd place Silver medal
3rd place Bronze medal

Medal icons draw the eye immediately when someone opens the leaderboard page. Being in the top 3 is a genuine achievement that members notice and compete for.

The Leaderboard Sidebar Widget

Add the Jetonomy: Leaderboard widget to any sidebar from Appearance → Widgets. The widget shows the top 5 members by reputation, listing each member's name and reputation score - a compact preview of the leaderboard without requiring members to visit the full page. (Its default heading text is "Top Members".)

Configuration options:

Option Default Description
Title Top Members Widget heading text
Count 5 Number of members to show (max 20)

The widget runs a single direct LIMIT query to fetch the top members by reputation.

Why the Leaderboard Improves Community Quality

Recognition is a powerful motivator. When members see their name on the leaderboard, they are more likely to write detailed answers, respond helpfully to new members, and keep coming back. Members who are close to moving up a rank are especially motivated - the leaderboard creates natural competition without requiring badges, gamification plugins, or manual awards.

Tip: Reference the leaderboard in your community welcome message or newsletter. "See who our top contributors are this month" gives members a reason to engage and something to aspire to.

What's Next?

Learn how the online status green dot works - when it shows, where it appears, and how Jetonomy tracks it efficiently.

Online Status →

The online status green dot shows which members are currently active in your community. It makes conversations feel more live and helps members know when is a good time to expect a quick reply.

User profile page showing avatar with online status indicator

What You Will Learn

  • When the green dot appears on a member's avatar
  • Where online status is displayed across the community
  • Where it is intentionally not shown and why
  • How Jetonomy tracks activity without overloading the database
  • That no setup is needed - it works automatically

When the Green Dot Appears

A green dot appears on a member's avatar when they have been active in the community within the last 5 minutes. Active means any authenticated page load or interaction - visiting a topic, posting a reply, casting a vote, or navigating between pages.

Close-up of a member avatar with the green online status dot in the lower corner

After 5 minutes of inactivity, the dot disappears automatically on the next page load that renders it.

There is no manual "appear offline" or "appear online" toggle. Online status reflects real activity.

Where Online Status Appears

The green dot is shown in these locations:

Location Shown
Reply cards (author avatar) Yes
Member profile page (header avatar) Yes
Sidebar Leaderboard widget No (the widget lists names and scores only, no avatars)
Topic listing rows (author credit) No
Search results (author credit) No

Why Not on Topic Listing Rows

Topic listing pages can show dozens of topics, each by a different author. Rendering the online status for every author on that page would require checking multiple records in a single request - potentially 20 to 50 lookups per page view on an active community.

Jetonomy deliberately omits the online dot from listing rows to keep those pages fast. The detail-level contexts (reply cards, profiles, widgets) are the right place for presence information because you are already focused on a specific member.

How Activity Tracking Works

When a logged-in member loads any community page, Jetonomy records a timestamp on their user record. To prevent a database write on every single page view, updates are rate-limited: Jetonomy writes the timestamp at most once per minute per member.

If a member loads 10 pages in 30 seconds, Jetonomy writes to the database once. This keeps the wp_jt_user_profiles table write volume proportional to the number of unique active members, not the total number of page views.

The online status check itself (reading whether a member is active) is cached for 60 seconds using the WordPress object cache. On a page with 10 reply cards by different authors, Jetonomy makes at most a small batch of cache reads rather than 10 individual queries.

No Setup Required

Online status is automatic. There is no setting to enable, no API key to configure, and no JavaScript polling. It works the moment Jetonomy is activated.

Note: Online status is only tracked for logged-in members. Guests who browse your community are not tracked and do not show an online indicator.

What's Next?

Go back to other sections, or return to the main community setup guide.

User Profiles → | Leaderboard →

Since Jetonomy 1.4.0, every signed-in member has a personal page at /community/my-spaces/ that lists every space they run and every space they're a member of, in one place. It is the fastest way to jump back into a space you are active in without scrolling the home page.

What You Will Learn

  • Where the My Spaces page lives and how to reach it
  • The two sections shown on the page and what they mean
  • What each row tells you at a glance
  • Quick actions available per row
  • What members see when they're brand new and have not joined any space yet
  • The privacy semantics: who can see this page

Where The Page Lives

The page is always at /community/my-spaces/. It is signed-in only. Visiting the URL while signed out redirects to the login page and returns to My Spaces after a successful sign-in.

There are two built-in ways to reach the page:

  • The My Spaces link in the header avatar menu (added automatically in Jetonomy 1.4.0+)
  • The mobile drawer menu under "Community"

If your theme overrides the header template, the link may not appear automatically. The page itself still works at the URL.

The Two Sections

My Spaces page showing the "Spaces you run" section with Admin and Mod role badges and Edit, Mod queue, and Members quick-action buttons, above the "Spaces you're in" section

The page is split into two sections, stacked top to bottom.

Spaces You Run

The first section lists every space where you are a space admin or space moderator. These are the spaces you have authority over.

For each space, the row shows:

  • The space icon and title
  • A role badge ("Admin" or "Mod")
  • The post count and member count
  • Quick action buttons: Edit (admins only), Mod queue, Members

The whole card is a link to the space home, so there is no separate "Visit" button.

If you run no spaces, this section simply does not appear (empty sections are hidden). If you also belong to no spaces, the page shows a single combined empty state - see Empty State below.

Spaces You're In

The second section lists every space where you are a regular member. These are the spaces you have joined but do not moderate.

For each space, the row shows:

  • The space icon and title
  • An optional short description
  • The post count and member count

The whole card is a link to the space home; member rows have no per-row action buttons.

If you have not joined any spaces yet, this section simply does not appear (empty sections are hidden).

What Each Row Tells You

Element What it means
Icon The Lucide icon picked by the space owner
Title The space name; the whole card links to the space home
Role badge "Admin" or "Mod" on the spaces you run; no badge for regular members
Description An optional short description excerpt, when the space has one
Post count Total published topics in the space
Member count Total members in the space

Quick Actions

Action buttons appear only on rows in the "Spaces you run" section. Member rows have no action buttons - clicking the card opens the space.

  • Edit appears only for spaces you administer. It opens the front-end Edit Space page covered in the previous article.
  • Mod queue opens the space's moderation queue.
  • Members opens the members tab where you can promote, demote, or remove members.

Empty State

Brand-new members often land on My Spaces before they have joined anything. When you neither run nor belong to any space, the page shows a single full-page empty state - "You are not in any spaces yet" with a Browse spaces button that goes to the community home.

Otherwise, empty sections are simply hidden: if you run spaces but belong to none as a regular member (or vice versa), only the section with content renders. There is no per-section "collapsed" placeholder and no "Create a space" button on this page.

Privacy

The My Spaces page is personal to the signed-in user.

  • It requires sign-in. Signed-out visitors are bounced to login.
  • It is excluded from search engine indexing via the noindex, nofollow meta tag.
  • It is not visible to anyone else. There is no public URL that shows another user's space list.
  • Membership in a Hidden space is shown on this page but is still not visible on the user's public profile.

If you want to see which spaces another user is in, you have to look at their public profile, which only shows public memberships.

Performance

The page loads all of your spaces with one indexed query per role bucket (the spaces you run, and the spaces you belong to). Space rows are hydrated once each, so there is no per-row N+1 query, and the per-card role label ("Admin" / "Mod") is served from a warmed cache.

Note: The page does not paginate. It loads every space you run and every space you belong to. For the typical member this is a handful of spaces; if you expect members to belong to hundreds of spaces, pagination here is a known gap rather than a shipped feature.

What's Next?

The My Spaces page is one entry into your community life. The full profile page covers the rest of what a member does: their posts, replies, votes, bookmarks, and drafts, alongside their reputation score and trust level. (#05-custom-badges-jtnmy) Pro extension.)

Your Profile Page →

Mobile App

Turn your community into a branded iOS and Android app with native push notifications.

Mobile App Overview

Jetonomy communities have a native iOS and Android app. Your members get a real mobile app for your forum - feed, topics, replies, votes, spaces, notifications, and (with Jetonomy Pro) messaging, reactions, polls, and badges - and it automatically shows your logo, color, and community name.

Requires Jetonomy 1.6.0 or newer for the per-site branding endpoint (/app/config). Older sites still work with default styling.

The Jetonomy mobile app home feed, themed with a community's own accent color, showing topics sorted by Hot, New, or Top

What you will learn

  • What the mobile app is and how it connects to your site
  • The two ways to ship it: the open-source app or your own branded app
  • Where to set your branding and how members sign in

How it works

The app connects directly to your WordPress site over the Jetonomy REST API (jetonomy/v1). There is no middleman server and no separate account system - members sign in with your site, and your data stays on your site.

Member's phone  ->  Jetonomy App  ->  Your WordPress site (jetonomy/v1 REST API)

When the app opens your community it reads your branding from GET /wp-json/jetonomy/v1/app/config (logo, accent color, community name) and themes itself to match.

Two ways to ship it

Option Best for What members see
Open-source Jetonomy app Trying it out, smaller communities, members who belong to several communities A generic app they point at your site; it themes itself with your branding
Your own branded app Established communities that want their own store presence Your icon and name in the App Store / Play Store, published under your own developer accounts

Both use the same codebase. You set the in-app branding (logo, color, name) the same way in either case - see Brand Your App.

What members can do

The Spaces screen in the mobile app, listing forums and Q&A spaces with category filters, member counts, and recent activity

  • Browse the feed across spaces (Hot / New / Top), post topics and questions, reply, and vote
  • Join forums, Q&A, and idea spaces; get notifications; search
  • With Jetonomy Pro: private messaging, reactions, polls, custom badges, custom fields, and native push
  • Belong to several communities and switch between them in one app
  • Read offline - the last-seen content is available without a connection

Next steps

The app is open source (GPL): github.com/vapvarun/jetonomy-app. Full step-by-step guides live in its wiki.

Brand Your App

The mobile app reads your branding straight from your WordPress settings, so you change it in wp-admin and the app updates itself - no rebuild needed. You control three things: your community name, your accent color, and your logo.

Requires Jetonomy 1.6.0 or newer. On older versions the app falls back to its default styling.

The mobile app sign-in screen branded from WordPress settings: the community logo, the community name, and the accent color applied to the sign-in button and links

What You Will Learn

  • Where to set the community name, accent color, and logo
  • How the app reads them and what each one affects
  • The difference between in-app branding and the phone home-screen icon

Set your community name

  1. Go to Jetonomy -> Settings -> General
  2. In Community Title, enter your community's name (for example, Course Academy)
  3. Click Save Changes

This becomes the community name shown in the app, including on the sign-in screen.

Set your accent color

  1. Go to Jetonomy -> Settings -> Appearance
  2. Under Color Palette, open Accent and pick your brand color
  3. Click Save Changes

The accent color themes the whole app - buttons, the active feed tab, links, and the compose button.

  1. Still on Jetonomy -> Settings -> Appearance, find the Logo field
  2. Paste the URL of your logo image
    • Tip: upload your logo under Media -> Add New, open it, and copy the File URL.
    • Best results: a transparent PNG at least 512px on its longest side
  3. Click Save Changes

The logo appears on the app's sign-in screen.

How the app reads it

The app fetches these values from the public endpoint:

GET /wp-json/jetonomy/v1/app/config

which returns your app_name (Community Title), accent_color, and logo_url. The app re-reads it whenever it connects, so updates appear without shipping a new build.

A community's accent color applied throughout the mobile app - the active feed tab, links, and the compose button all use the brand color

In-app branding vs the home-screen icon

  • In-app branding (logo, color, name) is what these settings control. It themes the inside of the app and works on the open-source app for every community.
  • The phone home-screen icon and app name are baked into the app binary. They only change when you publish your own branded app - see Get the App.

Next steps

Connect Members

There is no separate "connect your site" step. Everyone - you and your members - signs in to the app the same way, and WordPress decides who can do what. This page explains the sign-in flow and how roles work.

What You Will Learn

  • How the site address is provided (typed vs baked in)
  • How members create a secure Application Password
  • How admin and member roles are handled automatically

The site address

  • Open-source app: each person types your community's web address (for example, https://yourcommunity.com) once.
  • Your own branded app: the address is baked in, so members never type it - they just open your app and sign in.

How everyone signs in - Application Passwords

The app authenticates with WordPress Application Passwords, a secure feature built into WordPress core. It is not the member's normal password, and it can be revoked at any time without changing their real password.

Each person creates their own:

  1. Log in to your site's wp-admin
  2. Go to Users -> Profile and scroll to Application Passwords
  3. Enter a name like My Phone and click Add New Application Password
  4. WordPress shows a code like abcd efgh ijkl mnop - copy it
  5. In the app, enter the site address (if asked), the username, and paste that code

Application Passwords require your site to be served over HTTPS in production. Some security plugins disable them - if sign-in fails, confirm Application Passwords are enabled.

Admins vs members - automatic

The app reads each person's role from WordPress, so there is nothing extra to set up:

  • Admins and moderators also get a Manage area in the app (moderation queue, flags, announcements, analytics).
  • Members see the community (feed, topics, spaces, messages).

Being an admin in WordPress is the connection - no extra "register the app" action is needed.

A member's profile in the mobile app, showing their reputation, badges, and posts

Revoking access

Anyone can revoke a device from the same Users -> Profile -> Application Passwords screen, or sign out / remove the community inside the app. Revoking does not affect their account or their real password.

Next steps

Get the App

There are two ways to put the Jetonomy app in your members' hands: use the open-source app as-is, or publish your own branded app. Both use the same code and read your branding from WordPress.

What You Will Learn

  • How to try the app on a phone without publishing
  • What it takes to publish your own branded app
  • The accounts and tools involved (and the no-EAS alternatives)

The Communities screen, where a member can switch between several Jetonomy communities or add another - available in the open-source app

Try it first (no publishing)

The open-source app runs in Expo Go so you can see it before committing:

  1. Install Expo Go (free) on a phone from the App Store / Play Store
  2. On a computer with Node.js, run:
    git clone https://github.com/vapvarun/jetonomy-app.git
    cd jetonomy-app
    npm install
    npx expo start
    
  3. Scan the QR code with Expo Go (Android) or the Camera app (iPhone)
  4. Sign in with your site address + an Application Password

Publish your own branded app

This puts your app - your icon and name - in the App Store and Google Play under your own developer accounts.

You provide (one time):

  • A free Expo account
  • An Apple Developer account ($99/year) for the App Store
  • A Google Play account ($25 one time) for the Play Store

Then build and submit (full walkthrough in the app repo):

npm i -g eas-cli
eas login
eas init
eas build --profile production --platform all
eas submit

Not technical? You only need to create the three accounts. A developer - or the Wbcom team as a one-time setup - can do the rest and hand you the live app.

Prefer not to use Expo's cloud (EAS)?

The app is built with Expo, but EAS (Expo's paid cloud build service) is optional. You can build for free with eas build --local on your own Mac, or run npx expo prebuild and build the native projects in Xcode and Android Studio, or use Fastlane / Codemagic / GitHub Actions. A Mac with Xcode is required for iOS, and the Apple and Google accounts are required to publish (Apple's rules, not Expo's).

Full guides

The app is open source (GPL): github.com/vapvarun/jetonomy-app. Detailed, non-technical step-by-step guides - branding, signing in, trying the app, building, and troubleshooting - live in the wiki.

Next steps

Native Push Setup

Native push sends notifications to a member's phone even when the Jetonomy app is closed - a new reply, a mention, or a direct message shows up on the lock screen. It is delivered by the Web Push extension in Jetonomy Pro, which handles both browser push and native (Expo) push from the same settings.

Requires Jetonomy Pro with the Web Push extension enabled, plus Jetonomy 1.6.0 or newer for the mobile app. Native push also requires your site to be served over HTTPS.

What You Will Learn

  • What native push needs before it can work
  • Where to turn it on and set the notification title and icon
  • How members' devices get registered (nothing manual for you)

Before you start

Native push relies on three things being in place:

  1. Jetonomy Pro is installed and licensed.
  2. The Web Push extension is enabled under Jetonomy -> Extensions.
  3. Your site uses HTTPS. Push services reject insecure origins.

When the Web Push extension is active, the app's config endpoint (GET /wp-json/jetonomy/v1/app/config) reports features.native_push: true, and the app starts registering devices for push.

Turn on push and set the message

  1. Go to Jetonomy -> Settings and open the Web Push tab
  2. Tick Enable Web Push
  3. VAPID Public Key - this is generated automatically the first time the extension runs and shown here read-only. You do not need to create or paste a key. (If it reports it could not be generated, your server needs OpenSSL with EC support.)
  4. Default Notification Title - the title shown on push notifications. Defaults to your site name; change it to your community's name if you prefer.
  5. Notification Icon URL - the small icon shown with the notification. Recommended: a 192x192 PNG. Leave it blank to fall back to the site favicon.
  6. Click Save Web Push Settings

These same settings drive both browser push and native app push - there is no separate native-push screen to configure.

How members' devices get registered

You do not register devices by hand. When a member signs in on the Jetonomy app, the app sends its device push token to your site automatically:

POST /wp-json/jetonomy/v1/push/register-device

with the device's Expo push token and platform (ios or android). When a member signs out or uninstalls, the token is removed with a matching DELETE. From then on, notifications the member would see in-app are also delivered to their device.

In-app notifications vs native push

  • In-app notifications always work - the bell and the notifications screen inside the app.
  • Native push is the extra step that reaches the member when the app is closed. It only fires once the Web Push extension is enabled and saved, over HTTPS.

Next steps

Pro Features

Premium extensions: reactions, messaging, polls, badges, analytics, and more.

Activate your license, switch on the extensions you need, and understand which capabilities gate each Pro feature.

PRO - This section covers Jetonomy Pro.

What You Will Learn

  • How to activate your Jetonomy Pro license
  • How to enable and disable individual Pro extensions
  • Which license tier unlocks which extensions
  • Which WordPress capabilities gate each Pro feature

Activate Your License

Jetonomy Pro is a peer plugin to free Jetonomy. Install and activate free Jetonomy first, then install Jetonomy Pro - Pro shows an admin notice and stays dormant if the free plugin is not active.

  1. Go to Jetonomy → Settings → License in your WordPress admin (License is a tab inside the free plugin's Settings page, not a standalone page).
  2. Paste the license key from your purchase receipt.
  3. Click Activate. Jetonomy validates the key against the store and shows your tier and expiry.

Your license drives automatic updates and tier checks. If a license is missing or expired, extensions still boot - but features that require a higher tier than your license carries are blocked at the gate.

Note: Free and Pro always ship the same x.y.z version. Keep both updated together so the contracts between them stay in sync.

Enable Extensions

Jetonomy Pro ships seventeen extensions. None of them do anything until you switch them on.

  1. Go to Jetonomy → Extensions (the jetonomy-extensions page).
  2. Each extension shows a card with its name, description, and a toggle.
  3. Click Enable on the extensions you want. The toggle persists the choice to the jetonomy_pro_extensions option (an array of extension IDs).
  4. Enabling an extension runs any one-time setup it needs (for example, creating its database tables) and registers its hooks, REST routes, and admin screens immediately.

Disable an extension at any time from the same page. Disabling stops the feature and unregisters its hooks; it does not delete the data the extension already stored.

License Tiers

Tier Who it suits
Starter A single community getting started with Pro features
Growth A growing community that needs the full extension set across more sites
Agency Builders running Pro across many client sites
Lifetime One-time purchase with ongoing updates

All seventeen extensions are available on the paid tiers. Higher tiers raise the activation limits (number of sites) and support level rather than locking individual features behind a paywall. The exact site limits per tier are listed on your account page and on the pricing page.

Capabilities

Pro registers five capabilities that gate its features. Each maps to the WordPress roles shown below by default; you can reassign them with any role-editor plugin if your community uses custom roles.

Capability Default roles What it gates
jetonomy_manage_settings Administrator Access to the Pro settings pages (License, Extensions, integration and extension settings tabs)
jetonomy_manage_spaces Administrator Per-space Pro admin controls - SEO Pro metadata, custom-field tabs, and creating site-wide announcements
jetonomy_moderate Editor, Administrator The actions Advanced Moderation rules can take on matched content
jetonomy_vote Subscriber, Contributor, Author, Editor, Administrator Casting Reactions and voting in Polls
jetonomy_view_analytics Administrator Viewing the community Analytics dashboard

Note: Most Pro REST write routes are additionally gated by manage_options for admin operations, and by trust level for member operations (for example, Private Messaging and Poll creation require Trust Level 1 or higher). See the per-feature pages and the REST API reference for the exact permission on each route.

Data and Uninstall

Disabling an extension and deleting the plugin are two very different actions.

  • Disabling an extension (from Jetonomy → Extensions) only unregisters that extension's hooks and stops its background work. Everything it already stored - its tables, options, and member content - stays in place, so re-enabling it later picks up exactly where you left off. Deactivating the whole Jetonomy Pro plugin leaves all Pro data untouched the same way.
  • Deleting the plugin from the WordPress Plugins screen runs the uninstall routine. It removes Jetonomy Pro's database tables, Pro plugin options, Pro user meta (every jetonomy_pro_ key), and Pro scheduled tasks. Jetonomy Pro does not register its own WordPress capabilities - those belong to the free plugin and are cleaned up by its uninstall.

Only uninstalling removes stored data - and it is irreversible - so back up your database first if there is any chance you will want the data again.

What's Next?

Start with the engagement extensions members notice first.

Emoji Reactions →

Add expressive emoji reactions to every post and reply - so members can respond instantly without writing a full reply.

PRO - This feature requires Jetonomy Pro.

Reaction chips below a community post

What You Will Learn

  • How to enable Reactions for your community
  • How to customize which emojis appear per space
  • How members react to and change their reaction on a post
  • How to read reaction counts from the REST API

Why Reactions Matter

A quick reaction lowers the bar for engagement. Members who would never write a reply will tap a rocket emoji or a heart. That micro-engagement adds up - you get richer signal on your best content and members feel heard without the pressure of composing a response.

How It Works

Every post and every reply in your community shows a reaction strip. Members click any emoji to react. Clicking a different emoji replaces the previous one - each member can hold exactly one reaction per piece of content at a time. Clicking the same emoji again removes the reaction entirely.

The reaction counts are displayed as chips directly below the post body. Each chip shows the emoji and the total count. Hovering a chip reveals the names of recent reactors.

The reaction strip on a post, showing the eight default emoji options a member can pick from

Enabling Reactions

  1. Go to Jetonomy → Extensions in your WordPress admin.
  2. Find Reactions and click Enable.
  3. Reactions appear on all posts and replies immediately - no page-level configuration needed.

Tip: Enabling Reactions does not affect any existing posts. Historical content simply starts with zero reactions.

Default Emoji Set

Jetonomy ships with eight emoji reactions out of the box:

Emoji Label Use case
Like Thumbs up General agreement
Love Heart Enthusiasm, appreciation
Haha Laughing face Humor, something funny
Celebrate Party popper Wins, announcements
Thinking Thinking face Interesting, thought-provoking
Watching Eyes Following along, keeping an eye on it
Rocket Rocket Fast, shipped, love it
Dislike Thumbs down Disagreement

All eight are enabled globally by default. You can adjust which ones appear per space.

Per-Space Customization

Different spaces have different tones. A Support space might not need a Celebrate emoji, and a General Chat space might not need Dislike.

  1. Go to Jetonomy → Spaces and open the space you want to customize.
  2. Open the Reactions tab in the space settings panel.
  3. Toggle individual emojis on or off for this space.
  4. Save. The change takes effect immediately for all posts in that space.

The Reactions tab inside a space's settings panel, with a per-emoji on/off toggle for that space

REST API

The Reactions extension registers these endpoints under jetonomy/v1:

Method Endpoint Description
GET /posts/{id}/reactions Get the reactions and counts for a post
POST /posts/{id}/reactions Toggle your reaction on a post (add, replace, or remove)
GET /replies/{id}/reactions Get the reactions and counts for a reply
POST /replies/{id}/reactions Toggle your reaction on a reply

{id} is the numeric ID of the post or reply. A single POST toggles your reaction: sending a new emoji replaces your previous one, and sending your current emoji again clears it.

Example - toggle a reaction:

POST /wp-json/jetonomy/v1/posts/42/reactions
{
  "emoji": "rocket"
}

Reading reactions is open to any visitor; toggling a reaction requires the jetonomy_vote capability (logged-in members by default). See the REST API reference for full payloads.

What's Next?

Allow members to message each other privately, without leaving your community.

Private Messaging →

Let members send direct messages to each other - one-on-one or in small groups - without leaving your community.

PRO - This feature requires Jetonomy Pro.

Messages inbox showing conversation list

What You Will Learn

  • How to enable Private Messaging
  • How members start conversations and send messages
  • How unread counts and notifications work
  • How to block users from messaging you
  • How to use the REST API for conversations and messages

Why Private Messaging Matters

When members can message each other directly, your community becomes a platform - not just a forum. It reduces off-site communication, keeps relationships within your ecosystem, and gives you a richer, stickier product.

How It Works

Private Messaging adds a dedicated inbox at /community/messages/. Members can start a new conversation with any other member, or create a group conversation with up to 20 participants. Each conversation is a persistent thread - messages appear in chronological order, and new messages are loaded automatically via polling.

Single conversation thread view

Enabling Private Messaging

  1. Go to Jetonomy → Extensions in your WordPress admin.
  2. Find Private Messaging and click Enable.
  3. A Messages link appears automatically in the community navigation bar.

No additional configuration is required to go live.

Starting a Conversation

Members start a new conversation in two ways:

  • Click New Message from the Messages inbox at /community/messages/.
  • Click the Message button on any member's profile page.

Both methods open a composer. Members type the recipient's name (autocomplete searches by display name and username), write their first message, and hit Send. The conversation thread opens immediately.

For group conversations, members add multiple recipients before sending. The group conversation shows all participants' avatars at the top of the thread.

Group Conversations

A conversation is either direct (1:1) or a group with multiple participants. Add several recipients in the composer to start a group thread, and every participant sees the same shared history.

Group threads keep a complete record. When a member leaves a group, their past messages stay in place attributed to them with a "left the conversation" note - Jetonomy does not delete the messages, so the thread always reads coherently. Creating a direct message to someone you already have a 1:1 thread with reuses that existing thread instead of starting a duplicate.

Archive, Leave, and Mute

Each of these is a per-member setting - your choice never changes the conversation for the other participants.

  • Mute - silences notifications for a conversation while keeping it in your inbox. New messages still arrive; you just are not pinged. Unmute from the same menu.
  • Archive - hides a conversation from your main inbox to keep it tidy. Archived conversations stay fully intact and reappear (or can be reopened) when there is new activity; nothing is deleted.
  • Leave - removes you from a group conversation. You stop receiving its messages, but the thread and your past messages remain for everyone still in it. Leaving applies to group conversations.

Unread Counts and Notifications

A red badge on the Messages nav icon shows the total number of unread conversations. The count updates every 30 seconds via polling. It drops to zero when a member opens and reads the conversation.

Jetonomy also sends a notification to the recipient's bell icon when a new message arrives. Members who have email notifications enabled for private messages receive an email notification as well.

Tip: Members control their messaging email notifications in Profile → Notification Settings. Admins cannot override individual user preferences.

Blocking Users

Any member can block another from sending them messages:

  1. Open a conversation with the person.
  2. Click the ··· menu in the thread header.
  3. Select Block [username].

Blocked users cannot send new messages to the member who blocked them. Existing conversation history is preserved but no new messages are delivered. The blocked user sees a generic "Unable to send message" error - they are not told they are blocked.

Admins can view and clear blocks in Jetonomy → Users → [username] → Messaging.

Note (1.8.0+): This is separate from the community-wide blocking a member can do from the mobile app or REST API (see Blocking a Member), which hides a member's replies across the whole community rather than just messaging. As of 1.8.0, the two work together: Private Messaging also honors a community-wide block, so you cannot start or continue a conversation with someone you have blocked, or who has blocked you, even outside this menu.

REST API

Private Messaging adds endpoints under jetonomy/v1:

Method Endpoint Description
GET /conversations List your conversations (paginated, filterable)
POST /conversations Start a new direct or group conversation
GET /conversations/{id} Get conversation details and participant list
PATCH /conversations/{id} Update your settings on the conversation (mute)
GET /conversations/{id}/messages List messages (cursor-based pagination)
POST /conversations/{id}/messages Send a message
GET /conversations/unread-count Your total unread conversation count (30s cache)
POST /conversations/{id}/mute Mute or unmute the conversation for you
POST /conversations/{id}/archive Archive or unarchive the conversation for you
POST /conversations/{id}/leave Leave a group conversation
POST /conversations/{id}/block Block or unblock the other participant (direct only)

All endpoints require authentication, and member operations on a conversation require you to be a participant. Starting a conversation and sending messages require Trust Level 1 or higher (admins and moderators bypass this). See the REST API reference for full payloads.

Example - start a conversation:

POST /wp-json/jetonomy/v1/conversations
{
  "recipients": [45, 67],
  "message": "Hey, wanted to follow up on your question about onboarding."
}

Example - get messages with cursor pagination:

GET /wp-json/jetonomy/v1/conversations/12/messages?after=msg_abc123&per_page=20

Site-Owner Oversight (Conversations Admin Page)

New in 1.5.0. Site administrators get a dedicated Jetonomy → Conversations page in the WordPress admin sidebar for messaging oversight - useful for abuse investigations, GDPR requests, and verifying that messaging is healthy on a large community. The page is visible to administrators only (it requires the manage_options capability).

  • Conversations list - every conversation with its ID, type (direct or group), participants, message count, and last activity, most recently active first and paginated at 50 per page so it stays fast even with tens of thousands of threads.
  • Thread detail - click any conversation to read its full message history. Messages are shown newest first, paginated at 50 per page, each with the sender's display name (shown as "Deleted user" if the account no longer exists), a plain-text excerpt of the message (up to 300 characters), and the sent timestamp.
  • Purge - permanently delete a conversation and all of its messages and read-state rows. The button asks for confirmation ("Permanently delete this conversation for all participants?"); on confirm the conversation is removed, you return to the list with a "Conversation purged" notice, and the deletion fires the jetonomy_pro_conversation_purged action so audit tooling can record it.

Purging is irreversible and bypasses the participants' own archive/leave state - reserve it for moderation and compliance situations. It does not notify participants, does not touch their accounts or trust levels, and does not remove any posts or replies they made in public spaces.

Privacy: Site owners can read members' private conversations from this page. State this in your site's privacy policy so members understand your platform's moderation and compliance position.

What's Next?

Add polls to any topic to gather community input and drive decisions.

Polls →

Attach a poll to any topic and let your community vote - perfect for decisions, feedback, and feature prioritization.

PRO - This feature requires Jetonomy Pro.

A poll attached to a community post, showing each option as a horizontal percentage bar with its vote count

What You Will Learn

  • How to enable Polls
  • How to attach a poll when creating or editing a topic
  • How members vote, change their vote, and remove it
  • How poll results are displayed
  • How poll creators close a poll

Why Polls Matter

Asking a question in text is passive. Attaching a poll turns the same question into an action - members click an option in seconds instead of writing a response. You get quantified signal, higher engagement on that post, and a result the whole community can see at a glance.

Enabling Polls

  1. Go to Jetonomy → Extensions in your WordPress admin.
  2. Find Polls and click Enable.
  3. A + Add Poll button appears at the bottom of the post composer.

Creating a Poll

When writing a new topic, click + Add Poll beneath the content editor.

The poll builder lets you:

  • Write up to 20 options (minimum 2 required).
  • Choose Single choice (members pick one) or Multiple choice (members pick several).
  • Optionally set a Close date - the poll stops accepting votes automatically at that date and time.

The poll builder open in the post composer, with the option list, the single/multiple choice selector, and the optional close-date field The poll is attached to the topic and saved together when you click Post. You cannot attach a poll to a reply - only to top-level topics.

Tip: You can add or remove a poll from an existing topic by editing the post. Removing a poll permanently deletes all votes cast on it.

How Members Vote

The poll appears below the post body, before any replies. Each option is displayed as a labeled button.

  • Single choice - clicking an option records your vote immediately. You see the results as percentage bars as soon as you vote.
  • Multiple choice - checkboxes appear. Select all the options you want and click Vote.

After voting, members can change their vote by clicking a different option. Clicking your current selection a second time removes your vote entirely.

Members who have not voted see the options. Members who have voted see the live results. This design keeps undecided members from being anchored by early results.

Reading Results

Results display as horizontal percentage bars with the option label, the percentage, and the raw vote count. Bars fill in proportion to the leading option.

Voted poll results with horizontal percentage bars, each option's percentage and raw vote count, and the total vote tally below The total vote count appears below the bar chart. If the poll has a close date, a countdown shows how much time remains.

Closing a Poll

The topic author and any space moderator can close a poll at any time:

  1. Open the topic.
  2. Click the ··· menu on the poll card.
  3. Select Close Poll.

Once closed, the poll shows a Closed badge and no further votes are accepted. Existing results remain visible. A closed poll can be re-opened by the same users using the same menu.

Note: If you set a close date and later want to extend it, edit the post and update the date in the poll settings.

Multi-Select Voting

A poll is either single choice or multiple choice, set when you build it:

  • Single choice records exactly one option per member. Voting for a different option moves the vote.
  • Multiple choice lets each member pick several options at once. Each option toggles independently - a member can hold any number of selections, and clicking a chosen option a second time clears just that one.

Because of this, the total vote count on a multiple-choice poll can be higher than the number of voters - one member can contribute several votes.

Polls Admin

Enabling the extension adds a Polls submenu under the Jetonomy admin menu (jetonomy-pro-polls). It lists every poll in your community with its post, type, total votes, and close state, so you can review and close polls without opening each topic.

The Polls admin list page, showing every poll with its post, type, total votes, and close state

REST API

Polls registers these endpoints under jetonomy/v1:

Method Endpoint Description
POST /posts/{post_id}/poll Create a poll on a post
GET /posts/{post_id}/poll Get a poll's options and live vote counts
POST /polls/{id}/vote Cast a vote - pass option_id for single choice or option_ids (array) for multiple choice
DELETE /polls/{id}/vote Remove your vote(s) from the poll
PATCH /polls/{id} Update a poll (for example, change the close date)

Creating a poll and voting require the member to be logged in; poll creation additionally requires the right to post in the target space. See the REST API reference for full request and response payloads.

What's Next?

Collect structured information about your members with custom profile fields.

Custom Profile Fields →

Add structured fields to member profiles - collect the information that matters to your specific community.

PRO - This feature requires Jetonomy Pro.

Custom fields displayed on a member profile page

What You Will Learn

  • How to create and manage custom profile fields
  • Which field types are available
  • How to set visibility and required status
  • How members fill in their fields
  • How to read and update field values via the REST API

Why Custom Fields Matter

A generic WordPress profile has a bio and a website URL. That is not enough for most communities. A developer community needs a GitHub handle. A healthcare community needs a specialty. A SaaS community needs a company name. Custom Fields lets you define exactly the information that makes members useful and findable in your community.

Enabling Custom Fields

  1. Go to Jetonomy → Extensions in your WordPress admin.
  2. Find Custom Fields and click Enable.
  3. A Custom Fields item appears under the Jetonomy admin menu.

Creating a Field

  1. Go to Jetonomy → Custom Fields.
  2. Click Add Field.
  3. Fill in the field settings:
Setting Description
Label Displayed on the profile and in the edit form
Field Key Unique slug used in the REST API (auto-generated, editable)
Type See field types below
Required If on, members cannot save their profile without filling this in
Visibility Who can see the field value
Description Optional helper text shown below the input
  1. Click Save Field.

The Custom Fields admin page, listing existing fields with the Add Field form open showing the Label, Field Key, Type, Required, Visibility, and Description settings

Field Types

Type Best for
Text Short single-line answers (job title, company, username)
Textarea Longer free-form text (bio supplement, expertise description)
Number Numeric values (years of experience, team size)
Email Contact email address
URL Website, GitHub, LinkedIn, portfolio links
Select Predefined options as a dropdown, single choice (country, role, industry)
Checkbox Yes/no toggle (newsletter opt-in, open to work)
Radio Predefined options as radio buttons, single choice
Date A calendar date (joined date, availability)

For the Select and Radio types, you define the options in the field editor - one per line. Select renders a dropdown; Radio renders a set of radio buttons.

Visibility Options

Each field has one of three visibility settings:

Visibility Who sees the value
Public Anyone, including logged-out visitors
Members only Logged-in community members
Private Only the member themselves and admins

Private fields are still editable by the member but do not appear on their public profile. They are accessible to admins via the admin Users page.

How Members Fill In Fields

Members edit their custom fields at /community/u/{username}/edit/ under the Profile Details section. Required fields show a red asterisk. Saving the profile validates all required fields before updating.

Tip: Guide members to complete their profiles right after joining by linking to the edit profile page in your welcome notification or email digest.

REST API

Custom Fields adds field-aware parameters to the existing profile and post endpoints:

Method Endpoint Description
GET /users/{id} Profile response includes custom_fields object
GET /users List response includes custom_fields on each member
PATCH /users/{id} Pass custom_fields: { field_key: value } to update
GET /posts/{id} Post response includes custom_fields if any post-level fields are configured
GET /posts List response includes custom_fields on each post
GET /custom-fields List all defined fields and their settings
POST /custom-field-values Set a single field value on a target object (user or post)

Example - read a profile with custom fields:

GET /wp-json/jetonomy/v1/users/45

{
  "id": 45,
  "name": "Priya Sharma",
  "custom_fields": {
    "company": "Acme Corp",
    "github_username": "priyasharma",
    "open_to_work": true
  }
}

Example - update custom fields:

PATCH /wp-json/jetonomy/v1/users/45
{
  "custom_fields": {
    "company": "New Horizons Ltd"
  }
}

Members can only update their own fields. Admins can update any member's fields.

Setting one value at a time: if you want to set a single field on a single object rather than sending the whole custom_fields object, POST /custom-field-values with the field key, the target object type and ID, and the value. This is the lightweight path used by the edit-profile form and is handy for partial updates from custom tooling.

How fields surface in output: once a field is configured, its value is embedded automatically in the relevant REST responses - the custom_fields object on /users and /posts (and their list endpoints) - and rendered on the matching frontend surface (the member profile for profile fields, the post for post fields), subject to the field's visibility setting. You do not register a separate read endpoint per field; the value rides along with the object it belongs to.

See the REST API reference for full payloads.

Upgrading From Older Versions

If you are on Jetonomy 1.4.1 or newer, custom field values are returned automatically on the /jetonomy/v1/posts and /jetonomy/v1/users endpoints, so any tool reading the API sees every field you have configured. On older versions the values were saved to the database but did not appear in API responses - update both the free and Pro plugins together to read custom fields over the API.

What's Next?

Recognize and reward your most engaged members with custom badges.

Custom Badges →

Design your own badges, set the conditions that earn them, and watch members compete to collect them.

PRO - This feature requires Jetonomy Pro.

Badges displayed on a member profile page

What You Will Learn

  • How to create a badge with a name, icon, and tier
  • How to set auto-award conditions
  • How to award badges manually as an admin
  • How badges display on member profiles

Why Custom Badges Matter

Trust levels and reputation points are invisible to casual members. Badges are visible, collectible, and shareable - they give members a concrete goal to aim for. A "100 Posts" badge tells the community this member is active. A "Top Answerer" badge signals expertise. Badges convert passive lurkers into active contributors.

Enabling Custom Badges

  1. Go to Jetonomy → Extensions in your WordPress admin.
  2. Find Custom Badges and click Enable.
  3. A Badges item appears under the Jetonomy admin menu.

Creating a Badge

  1. Go to Jetonomy → Badges.
  2. Click Create Badge.
  3. Fill in the badge details:
Field Description
Name Displayed on the badge and in the award notification
Description One sentence explaining how to earn it
Icon Upload a 64×64 PNG or SVG icon
Tier Bronze, Silver, or Gold - controls the border color on profile
  1. Set the award conditions (see below).
  2. Click Save Badge.

The Create Badge editor, showing the name, description, icon upload, Bronze/Silver/Gold tier selector, and auto-award criteria settings

Award Conditions

Auto-Award

Auto-awarded badges evaluate all members on a regular schedule and grant the badge automatically when the conditions are met. Choose from built-in criteria:

Criteria Example threshold
Total posts 10, 50, 100, 500 posts
Accepted answers 5, 25, 50 accepted answers
Total replies 25, 100, 250 replies
Upvotes received 10, 50, 200 upvotes on any content
Days as member 30, 180, 365 days since joining
Reputation 100, 500, 1000 reputation points
Trust level Trust level 1 through 5
Spaces joined 3, 10, 25 spaces joined

Set the threshold for each criteria you want to use. You can combine multiple criteria with all (the member must satisfy every condition) or any (one is enough) to earn the badge.

Note: Auto-evaluation runs automatically - a recurring job every 6 hours (via Action Scheduler, with a WP-Cron fallback), plus event-driven re-evaluation that fires when a member's posts, replies, votes, reputation, or trust level change. There is no manual "evaluate now" button.

Manual Award

Some badges should not be automated - "Staff Pick", "Most Helpful in July", or "Community Founder" are judgment calls. For these, leave all auto-award criteria blank and award manually:

  1. Go to Jetonomy → Users and open the member's profile.
  2. Click Award Badge.
  3. Select the badge from the list and add an optional private note.
  4. Click Award.

The member receives a notification immediately and the badge appears on their profile.

Badge Tiers

Badges have three visual tiers that appear as border colors on the badge icon:

Tier Color Suggested use
Bronze Warm bronze Entry-level milestones (first post, 7-day streak)
Silver Cool silver Mid-tier milestones (100 posts, 10 accepted answers)
Gold Bright gold Elite milestones (500 posts, Top Contributor of the year)

Tiers are visual only - they do not affect permissions or trust levels.

Badge Display

Badges appear on member profile pages in a dedicated Badges section. Members who have earned no badges see an empty state that lists a few featured badges to work toward - this passively encourages engagement.

The three most recently earned badges also appear in the member's hover card, which pops up when anyone hovers their username throughout the community.

REST API

Custom Badges registers these endpoints under jetonomy/v1:

Method Endpoint Description
GET /badges List all defined badges and their settings
POST /badges Create a badge
PATCH /badges/{id} Update a badge
DELETE /badges/{id} Delete a badge
POST /users/{id}/badges Award a badge to a member

Badges can be created, edited, and awarded entirely through REST, so you can automate awards from your own tooling or grant a badge as part of an external workflow. Listing badges is open to any logged-in member; creating, editing, deleting, and awarding require manage_options. See the REST API reference for full payloads.

What's Next?

Get a data-driven view of your community's health with the Analytics Dashboard.

Analytics Dashboard →

Understand what your community is doing, where it is growing, and who is driving it - all from a single admin dashboard.

PRO - This feature requires Jetonomy Pro.

Analytics dashboard showing charts and top contributor table

What You Will Learn

  • How to access the Analytics dashboard
  • Which metrics are tracked and what they mean
  • How to use the date range filter
  • How to export data as CSV

Why Analytics Matter

You cannot grow what you cannot measure. The Analytics dashboard turns raw community activity into actionable metrics - so you know which spaces need attention, who your power users are, and whether engagement is trending up or down before it becomes a problem.

Enabling Analytics

  1. Go to Jetonomy → Extensions in your WordPress admin.
  2. Find Analytics and click Enable.
  3. An Analytics item appears under the Jetonomy admin menu.

Analytics begin recording from the moment you enable the extension. Historical data before activation is not backfilled.

Go to Jetonomy → Analytics. The dashboard opens on the last 30 days by default.

Date Range Filter

Use the date range picker at the top right to select any custom range. Preset shortcuts:

  • Last 7 days
  • Last 30 days
  • Last 90 days
  • This month
  • Last month

All charts and tables update instantly when you change the range.

The analytics date-range picker, showing the preset shortcuts (Last 7 days, Last 30 days, Last 90 days, This month, Last month) and the custom range option

Overview Metrics

The top row shows four headline numbers for your selected range:

Metric What it means
Total Posts New topics created in the period
Total Replies New replies created in the period
Active Members Unique members who posted, replied, or voted
Growth Rate Percentage change vs the previous equal-length period

Below the headline row, a line chart shows daily post and reply volume over the selected range. Spikes are easy to spot - hover any point to see the exact date and count.

Counting semantics (improved in 1.5.0): the daily totals count published content and stay accurate through the whole content lifecycle. A post created as pending counts on the day it was created once a moderator approves it; trashing a published post removes it from its original day's total (never producing negative days); restoring adds it back. Earlier versions counted at creation only, so moderated or trashed content slowly drifted the totals.

Top Spaces

A ranked table shows your most active spaces sorted by total posts + replies in the period. Columns include post count, reply count, unique contributors, and engagement rate (replies per post).

Use this view to find spaces that are thriving - and spaces that have gone quiet and may need a prompt or a featured topic.

Top Contributors

A ranked table of your most active members sorted by total contributions (posts + replies + accepted answers). Each row shows the member's avatar, display name, trust level, and contribution breakdown.

Tip: Use this list to identify members to invite into a moderator role, feature in a community spotlight, or send a personal thank-you.

Engagement Metrics

The engagement section shows:

Metric What it means
Avg. replies per post How much discussion each new topic generates
Vote activity Total upvotes and downvotes cast
Accepted answers Q&A spaces only - rate of questions getting resolved
New member joins Community growth over the period

Moderation Stats

The moderation section shows flagged content volume, auto-moderation rule triggers (if Advanced Moderation is enabled), and moderator response time. A high flag volume combined with slow response time signals you need more moderators.

CSV Export

Click Export CSV in the top right of any table to download a full data export for the current date range. Exports include all rows - not just the visible page.

Exported files are compatible with Excel, Google Sheets, and any BI tool. Column headers match the on-screen labels exactly.

Dual-Path Aggregator (1.4.1, observation only)

In 1.4.1 a second analytics path runs quietly alongside the existing direct-query reader. It's an event-driven aggregator that writes to a new internal table (jt_pro_analytics_aggregate) on every relevant community event, instead of querying core tables every time the dashboard loads.

The aggregator is in a 7-day observation window. Your dashboards still read from the original direct-query path in 1.4.1 - there is no public-facing behaviour change yet. The point of the observation window is to verify that the two paths agree before flipping the default.

Verify Dual-Path admin toggle

Go to Jetonomy → Analytics. The Pro Analytics page now has a Verify dual-path toggle. Turn it on and every metric on the page is shown twice - once from the direct-query reader, once from the aggregator - with a drift percentage between them. Use this if you want to spot-check the aggregator's accuracy before we promote it to default.

GET /jetonomy/v1/analytics/diff-report

For programmatic comparison, the same data is available at:

GET /wp-json/jetonomy/v1/analytics/diff-report?range=30d

{
  "range": "30d",
  "metrics": [
    { "key": "total_posts", "direct": 1284, "aggregated": 1284, "drift_pct": 0 },
    { "key": "active_members", "direct": 312, "aggregated": 311, "drift_pct": -0.32 }
  ]
}

Helpful if you want to ship a custom monitoring panel during the observation window.

What's Next?

Reduce your moderation workload by automating common moderation decisions.

Advanced Moderation Rules →

Define rules that catch bad content automatically - before it ever appears in your community.

PRO - This feature requires Jetonomy Pro.

What You Will Learn

  • How to enable Advanced Moderation Rules
  • How to create keyword, regex, link-limit, and spam-score rules
  • What actions each rule can take on matched content
  • How to scope rules to a specific space or apply them globally
  • How to read rule trigger statistics

Why Auto-Moderation Matters

Manual moderation does not scale. A single moderator reviewing every post works fine at 10 posts per day - it fails at 1,000. Auto-moderation rules handle the obvious cases automatically so your human moderators can focus on edge cases that require judgment.

Advanced Moderation complements the free trust level system. New members with Trust Level 0 are already rate-limited. Auto-moderation rules add a content layer on top of that.

Enabling Advanced Moderation

  1. Go to Jetonomy → Extensions in your WordPress admin.
  2. Find Advanced Moderation and click Enable.
  3. A Moderation Rules tab appears under Jetonomy → Moderation.

Creating a Rule

  1. Go to Jetonomy → Moderation → Rules.
  2. Click Add Rule.
  3. Configure the rule:
Setting Description
Name Internal label - members never see this
Pattern type Keyword, Regex, Link limit, or Spam score
Pattern The word, phrase, regex, or threshold to match
Action What happens when the rule triggers
Scope Global (all spaces) or a specific space
Active Enable or disable the rule without deleting it
  1. Click Save Rule.

Pattern Types

Keyword

Matches any post or reply body that contains the exact word or phrase (case-insensitive). Use comma-separated values to match any of several words with a single rule.

Example: buy now, click here, limited offer

Regex

Full regular expression matched against the post body. Use this for patterns a keyword list cannot capture - phone number patterns, URL shortener patterns, or obfuscated spam.

Example: \b(\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b

Note: Regex patterns are evaluated server-side using PHP preg_match(). Test your regex at regex101.com before adding it to a live rule.

Triggers when a post or reply contains more than a set number of links. New spammers often post content with 5-10 outbound links. A limit of 3 catches most of these while allowing legitimate "here are some resources" posts.

Spam Score

Jetonomy calculates a spam probability score (0-100) for each post based on content patterns, account age, and posting frequency. Set a threshold - any post above that score triggers the rule.

A threshold of 80 is a good starting point. Lower it only if you are seeing spam slip through.

Rule Actions

Action What happens
Flag Content publishes normally and is added to the mod queue with a flag
Hold Content is held as Pending and does not appear until a moderator approves it
Block The post is rejected and the member sees an error message
Spam Content is marked as spam and hidden immediately

Choose the least restrictive action that solves the problem. Use Flag for borderline content, Hold for likely-bad content, and Block or Spam for clearly harmful content.

Rule Scope

Global rules apply to every post and reply across all spaces. Use these for site-wide policies - prohibited words, adult content, competitor spam.

Space-scoped rules apply only within a specific space. Use these for space-specific norms - a Support space might block all links to prevent fishing attacks, while General Chat allows them freely.

Rule Statistics

The rules list shows a Triggered count for each rule - how many times it has fired since the rule was created. Click a rule to see a breakdown by action, by space, and a timeline chart.

Use this data to tune your rules. A rule that triggers 500 times in a week and sends everything to Spam probably needs a higher threshold - it is catching legitimate content.

REST API

Advanced Moderation manages rules under jetonomy/v1:

Method Endpoint Description
GET /moderation/rules List all moderation rules
POST /moderation/rules Create a rule
PATCH /moderation/rules/{id} Update a rule
DELETE /moderation/rules/{id} Delete a rule
GET /moderation/rules/{id}/stats Get a rule's trigger statistics

All routes require manage_options. See the REST API reference for full payloads.

What's Next?

Re-engage members who have not visited recently with automated email digests.

Email Digest →

Send a curated summary of community activity to members' inboxes - daily or weekly - so they never feel out of the loop.

PRO - This feature requires Jetonomy Pro.

Email digest preview showing top posts from the week

What You Will Learn

  • How to enable and configure the Email Digest
  • What content appears in each digest
  • How members control their digest frequency
  • How to send a test digest as an admin

Why Email Digest Matters

Most community members are not daily visitors. They join, participate a few times, and drift away - not because they lost interest, but because they forgot. A well-timed email digest brings them back. It shows them what they missed and gives them a reason to click.

Enabling Email Digest

  1. Go to Jetonomy → Extensions in your WordPress admin.
  2. Find Email Digest and click Enable.
  3. Go to Jetonomy → Settings → Email Digest to configure sending times and content rules.

Configuring the Digest

Send Schedule

Set when the digests go out:

Digest type Default send time
Daily 8:00 AM (site timezone)
Weekly Monday, 8:00 AM (site timezone)

You can change both send times in the Email Digest settings. Times use the timezone set in Settings → General → Timezone in WordPress.

Digest Content

The digest compiles:

  • Top posts - the most-upvoted new topics since the last digest
  • Active discussions - posts with the most replies in the period
  • Spaces you follow - activity in spaces the member has joined or bookmarked
  • Replies to your topics - new replies on topics the member created or commented on
  • 🏆 Badges earned (new in 1.4.1) - every badge the member earned during the digest window
  • 🗳️ Polls voted on (new in 1.4.1) - every poll the member participated in during the window

You can toggle each content section on or off in the digest settings. At least one section must remain on.

How the new sections work: badges and polls are tracked in a per-user buffer that's capped at 100 events with a 30-day TTL. The buffer is cleared only after a successful send, so opted-out members never accumulate state, and a missed send doesn't lose the activity.

The Jetonomy, Settings, Email Digest panel, showing the daily and weekly send-time fields and the per-section content toggles

Tip: Keeping "Replies to your topics" on is the single most effective setting. Members always care more about activity on their own posts than about the broader community.

Member Notification Preferences

Each member controls their own digest frequency from Profile → Notification Settings → Email:

Option What it means
Instant Individual emails per event (free behavior)
Daily digest One email per day summarizing activity
Weekly digest One email per week summarizing activity
None No community emails

Members who select Daily or Weekly stop receiving per-event notification emails. The digest replaces them - they are not sent in addition to them.

New members default to Daily digest. You can change this default in the Email Digest settings.

Admin Test Send

Before going live, send yourself a test digest to check formatting and content:

  1. Go to Jetonomy → Settings → Email Digest.
  2. Scroll to the Test Send section.
  3. Enter an email address (pre-filled with your admin email).
  4. Click Send Test Digest.

The test digest uses real community data from the last 7 days. If your community is new and has little activity, the digest may look sparse - that is expected.

Digest Statistics

The Email Digest settings page shows send statistics for the last 90 days:

Stat Description
Digests sent Total number of emails delivered
Preference breakdown How many members are on daily vs weekly vs none

Open rates and click rates are available if you use a supported ESP adapter (SendGrid, Mailgun, SES, or Postmark) - basic wp_mail delivery does not provide tracking data.

REST API

Email Digest exposes endpoints under jetonomy/v1:

Method Endpoint Description
GET /users/me/digest-preferences Read the current member's digest frequency and section choices
PATCH /users/me/digest-preferences Update the current member's digest preferences
POST /admin/digest/test Send a test digest (admin)
GET /admin/digest/stats Read digest send statistics (admin)

Members manage their own preferences; the /admin/* routes require manage_options. See the REST API reference for full payloads.

What's Next?

Connect your community to external tools like Slack, CRMs, and Zapier using outbound webhooks.

Outbound Webhooks →

Automatically send community events to any external URL - connect Jetonomy to Slack, Zapier, your CRM, or any custom pipeline.

PRO - This feature requires Jetonomy Pro.

Webhook management page listing configured endpoints

What You Will Learn

  • How to create and manage webhook endpoints
  • Which community events you can subscribe to
  • How to test a webhook before going live
  • How to read the delivery log to debug failures

Why Webhooks Matter

Jetonomy lives inside WordPress - but your business does not. Your team lives in Slack. Your sales data lives in a CRM. Your analytics live in a data warehouse. Webhooks close that gap. Every time something happens in your community, Jetonomy can push that data wherever you need it - in real time, with zero polling.

Enabling Webhooks

  1. Go to Jetonomy → Extensions in your WordPress admin.
  2. Find Outbound Webhooks and click Enable.
  3. A Webhooks item appears under the Jetonomy admin menu.

Creating a Webhook

  1. Go to Jetonomy → Webhooks.
  2. Click Add Webhook.
  3. Fill in the endpoint settings:
Setting Description
URL The HTTPS endpoint that will receive the POST request
Events Which community events trigger this webhook
Secret Optional signing secret for verifying payload authenticity
Active Toggle the webhook on or off without deleting it
  1. Click Save Webhook.

The Add Webhook form, with the URL, Events, Secret, and Active fields

Available Events

Subscribe to any combination of these events:

Event Fires when...
post.created A new topic is published
post.updated A topic is edited
post.deleted A topic is deleted or trashed
reply.created A new reply is posted
reply.updated A reply is edited
reply.deleted A reply is deleted or trashed
vote.cast A member votes on a post or reply
answer.accepted A Q&A answer is accepted
member.joined A new user joins the community
member.left A member leaves a space
member.banned A member is banned
user.registered A new user account is created
trust_level.changed A member is promoted or demoted
flag.created Content is flagged by a member
flag.resolved A flag is resolved by a moderator
moderation.action A moderator approves, spams, or trashes content

You can create multiple webhooks pointing to different URLs with different event subsets - for example, one webhook for Slack (post events only) and one for your CRM (member events only).

Payload Format

Every webhook delivers a JSON body with this structure:

{
  "event": "post.created",
  "timestamp": "2026-03-26T08:14:32Z",
  "site_url": "https://yoursite.com",
  "data": {
    "post_id": 1024,
    "title": "How do I reset my password?",
    "author_id": 45,
    "author_name": "Priya Sharma",
    "space_id": 3,
    "space_slug": "support",
    "url": "https://yoursite.com/community/s/support/t/how-do-i-reset-my-password/"
  }
}

The data object varies by event type. All events include event, timestamp, and site_url.

Verifying Payloads

If you set a secret, Jetonomy includes an X-Jetonomy-Signature header with each request. The value is an HMAC-SHA256 signature of the raw request body, signed with your secret.

Verify on your server:

$signature = hash_hmac( 'sha256', $raw_body, $your_secret );
$expected   = 'sha256=' . $signature;
$received   = $_SERVER['HTTP_X_JETONOMY_SIGNATURE'];

if ( ! hash_equals( $expected, $received ) ) {
    http_response_code( 401 );
    exit;
}

Testing a Webhook

Before you point a webhook at a production system, send a test:

  1. Open the webhook in Jetonomy → Webhooks.
  2. Click Send Test.
  3. Jetonomy sends a sample ping event to your URL and shows the HTTP response code and body inline.

Use a tool like webhook.site to inspect the full request during development.

Delivery Log

Every delivery attempt is logged. Go to Jetonomy → Webhooks → [webhook name] → Delivery Log to see:

  • Timestamp of each attempt
  • HTTP response code
  • Response body (first 500 characters)
  • Whether the delivery succeeded or failed

Failed deliveries are retried up to three times with exponential backoff (5 min, 30 min, 2 hrs). After the third failure, the delivery is marked as permanently failed and no further retries are attempted.

Tip: If you see consistent failures, check that your endpoint returns a 2xx response within 10 seconds. Jetonomy times out at 10 seconds and treats the delivery as failed.

Use Case Examples

  • Slack notifications - Post a message to a Slack channel whenever a new topic is created in your community.
  • CRM sync - Push member.joined events to HubSpot or Salesforce to trigger a welcome sequence.
  • Zapier - Connect any event to thousands of apps via a Zapier webhook trigger - no custom code needed.
  • Analytics pipeline - Stream all events to a data warehouse like BigQuery or Amplitude for long-term retention and custom analysis.

Upgrading From Older Versions

On Jetonomy 1.4.1 and newer every event in the list above fires reliably for real community activity. If you configured webhooks on an older version and saw zero deliveries despite real activity, update both the free and Pro plugins together to 1.4.1 or later - your existing webhooks start delivering without any reconfiguration.

What's Next?

Send browser push notifications to members even when they are not on your site.

Web Push Notifications →

Reach members with browser push notifications - even when they have closed your site.

PRO - This feature requires Jetonomy Pro.

What You Will Learn

  • How to generate VAPID keys and enable Web Push
  • How members subscribe to push notifications
  • Which events trigger a push notification
  • Which browsers are supported

Why Web Push Matters

Email notifications have open rates around 20-30%. Browser push notifications have open rates above 60% because they appear immediately on the member's screen - no inbox, no subject line, no waiting. For time-sensitive events like a reply to your question or a mention from a teammate, push gets the message there instantly.

Enabling Web Push

Web Push requires a VAPID key pair to authenticate your server with browsers.

  1. Go to Jetonomy → Settings → Web Push.
  2. Click Generate VAPID Keys. Jetonomy creates a public/private key pair and stores them in your WordPress options table.
  3. Toggle Enable Web Push to on.
  4. Click Save.

Important: VAPID keys are generated once. If you regenerate them, all existing push subscriptions are invalidated and members must subscribe again. Only regenerate if you believe your private key has been compromised.

Web Push settings page with VAPID key fields and enable toggle

Service Worker Registration

Jetonomy automatically registers a service worker (/community/sw.js) on every community page. You do not need to create or configure the service worker - this happens at extension activation.

The service worker handles:

  • Receiving push messages from the Jetonomy server
  • Displaying the browser notification
  • Opening the correct URL when the notification is clicked

Note: Every push payload is encrypted per RFC 8291 (aes128gcm content encoding) before it leaves your server, so browsers and push services accept it and its contents are never readable in transit.

Looking for push on a native iOS or Android app instead of the browser? The same extension registers app devices for native push - see Native push setup.

Member Subscription

The first time a logged-in member visits any community page after you enable Web Push, a Enable push notifications prompt appears at the top of the page. Clicking Enable triggers the browser's native permission dialog.

Members who grant permission are subscribed automatically. Their subscription is stored in the Jetonomy database and associated with their account.

Members can unsubscribe at any time from Profile → Notification Settings → Push Notifications → Off.

Note: The browser permission prompt can only be triggered by a user action (a click). Jetonomy waits for the member to interact with the prompt banner before requesting permission - it never requests permission automatically on page load.

Notification Events

Push notifications are sent for the same events that trigger bell notifications, based on each member's preferences:

Event Who receives a push
New reply on your topic Topic author
New reply in a topic you follow Followers
You are mentioned Mentioned member
Your answer is accepted Answer author
New message received Message recipient (Private Messaging Pro)
Badge awarded Badge recipient

Members control which of these events trigger a push in Profile → Notification Settings.

Browser Support

Web Push works on all major modern browsers without any app installation:

Browser Desktop Mobile
Chrome Yes Yes (Android)
Edge Yes Yes (Android)
Firefox Yes Limited
Safari Yes (macOS 13+) Yes (iOS 16.4+)

Tip: Safari on iOS requires members to add your site to their Home Screen before push notifications work. This is an Apple platform limitation - not a Jetonomy limitation.

REST API

Web Push registers these endpoints under jetonomy/v1:

Method Endpoint Description
POST /push/subscribe Register the current member's browser push subscription
DELETE /push/subscribe Remove the current member's push subscription
GET /push/vapid-key Fetch the public VAPID key the browser needs to subscribe
GET /push/service-worker.js Serve the service worker script

Subscribe and unsubscribe require the member to be logged in; the VAPID key and service worker are served for the page to register push. See the REST API reference for full payloads.

What's Next?

Let members reply to community topics directly from their email client.

Reply by Email →

Let members reply to community discussions directly from their email client - no login required for that one reply.

PRO - This feature requires Jetonomy Pro.

What You Will Learn

  • How Reply by Email works end to end
  • How to configure the inbound email endpoint
  • How emails are parsed and turned into replies
  • How to test the feature and handle parsing errors

Why Reply by Email Matters

Every step between "I got a notification" and "I posted a reply" loses members. Reply by Email removes all those steps. The member reads the notification in their inbox, types a reply directly, hits Send, and the reply appears in the community - without opening a browser or logging in. Removing that friction increases reply volume noticeably.

How It Works

  1. Jetonomy sends a notification email for a new reply or mention.
  2. The email contains a Reply to this post call to action with a unique reply-to address.
  3. The member replies to that email.
  4. The inbound email is delivered to your configured endpoint.
  5. Jetonomy parses the email, strips quoted content, and creates a community reply attributed to that member.

Each reply-to address is unique to the member and the topic - it encodes an authentication token so no login is required.

Configuration

Reply by Email requires an inbound email endpoint - a URL that receives incoming emails from your email provider. Most email providers (SendGrid Inbound Parse, Mailgun Inbound Routes, Postmark Inbound, or Amazon SES with SNS) can forward inbound email as an HTTP POST to a URL.

Step 1: Get Your Inbound Endpoint URL

  1. Go to Jetonomy → Settings → Reply by Email.
  2. Copy the Inbound Endpoint URL. It looks like:
https://yoursite.com/wp-json/jetonomy/v1/email/inbound

Step 2: Configure Your Email Provider

Point your email provider's inbound parsing feature at the Jetonomy endpoint URL. The exact steps vary by provider - follow your provider's documentation for "inbound email parsing" or "inbound routing."

Set up a dedicated inbound domain or subdomain for replies. Example: reply.yoursite.com. Your provider resolves inbound mail sent to *@reply.yoursite.com and forwards the parsed payload to your Jetonomy endpoint.

Step 3: Enter Your Reply Domain

Back in Jetonomy → Settings → Reply by Email, enter the reply domain (e.g. reply.yoursite.com) and click Save.

Jetonomy now generates per-user, per-topic reply addresses using that domain.

The Jetonomy, Settings, Reply by Email screen, showing the Inbound Endpoint URL and the reply domain field

Email Parsing

Jetonomy parses the incoming email using these rules:

  1. Strip quoted content - Lines that begin with > (standard email quoting) are removed. The reply contains only the new text the member typed.
  2. Plain text preferred - If the email has a plain text part, Jetonomy uses that. If not, it strips HTML and uses the text content.
  3. Basic formatting preserved - Line breaks are preserved. Links in the plain text body are converted to Markdown links.
  4. Attachments ignored - Image and file attachments in reply emails are not processed in v1.0.

The parsed reply text goes through the same wp_kses_post sanitization as any other reply before it is saved.

Parsing Rules Configuration

You can adjust how Jetonomy handles edge cases:

Setting Default Description
Min reply length 5 characters Rejects replies shorter than this (catches accidental sends)
Max reply length 10,000 characters Truncates anything longer
Strip signatures On Removes common signature separators (-- , Sent from my iPhone, etc.)

Security

Each reply-to address contains a signed token that ties the email address to a specific WordPress user and topic. Jetonomy verifies the token before creating any reply. An attacker who intercepts or guesses a reply address cannot post as another member - the token is cryptographically bound to the user ID.

Tokens expire after 30 days. Notification emails older than 30 days cannot be replied to by email.

Testing Reply by Email

  1. Go to Jetonomy → Settings → Reply by Email.
  2. Click Send Test Email to send a sample notification to your admin email address.
  3. Reply to that email with any text.
  4. Return to the admin and click Check Last Inbound to see the parsed result.

What's Next?

Remove all Jetonomy branding and present the community as entirely your own.

White Label & Branding →

Remove all Jetonomy branding and present your community as entirely your own product.

PRO - This feature requires Jetonomy Pro.

What You Will Learn

  • How to enable White Label
  • How to remove Jetonomy branding from the frontend and admin
  • How to set a custom admin menu label and icon
  • How to rebrand transactional emails and digests

Why White Label Matters

You built your community. Your members know your brand - not the plugin powering it. White Label means your community looks like yours from every angle: the frontend pages, the admin sidebar, and the notification emails. This is especially important for agencies delivering client projects and for SaaS products embedding community features under their own brand.

Enabling White Label

  1. Go to Jetonomy → Extensions in your WordPress admin.
  2. Find White Label and click Enable.
  3. A Branding tab appears under Jetonomy → Settings.

Removing Frontend Branding

Go to Jetonomy → Settings → Branding.

Setting Default What it controls
"Powered by Jetonomy" footer Shown Remove the attribution link from the community footer
Jetonomy logo in community nav Shown Replace with your own logo or hide entirely
Custom CSS injection Empty Add CSS that loads on every community page

Upload your own logo (SVG or PNG, max 400×100 px) to replace the Jetonomy logo in the community navigation bar. Leave the logo field blank to show no logo at all.

White Label branding settings panel

Tip: Use the Custom CSS injection field to apply brand-specific color overrides without editing any theme files. The CSS injects after Jetonomy's own stylesheet so your values always win.

Admin Menu Customization

By default, the Jetonomy admin menu item is labeled "Jetonomy" with the Jetonomy logo icon.

In Jetonomy → Settings → Branding → Admin Menu:

  • Menu label - Change to any string (e.g. "Community", "Forum", "My Community").
  • Menu icon - Enter any Dashicons class (e.g. dashicons-groups) or leave blank to use the default.

The label change applies to the top-level menu item and the browser window title on all Jetonomy admin pages.

Email Branding

White Label also affects transactional emails and digests. In Settings → Branding → Email:

  • From name - Defaults to your site name. Change to any value.
  • Email footer - Replaces the default Jetonomy email footer with your own text or HTML.
  • Logo in emails - Upload a logo displayed at the top of notification emails.

Branding Settings Reference

White Label stores its configuration in the jetonomy_pro_white_label option. The settings worth calling out:

Setting What it does
header_logo_url The logo actually displayed in the community header. This is the image members see at the top of every community page.
logo_url The fallback / Open Graph logo. Used for social share cards (OG image) and anywhere a logo is needed but no header logo is set. Keep this set even if you customize the header separately.
accent_color A global accent colour override. Recolours Pro-rebranded surfaces - including email accents - in one place so your brand colour is consistent across the community and its emails.
custom_css Freeform CSS injected on every community page. It loads after Jetonomy's own stylesheet, so your rules always win - use it for brand colour and spacing tweaks without touching theme files.
sidebar_auth_card_html Custom HTML for the community sidebar's sign-in / call-to-action card. When set, it replaces Jetonomy's default auth card with your own markup; leave it empty to keep the default card.
footer_text Replaces the "Powered by Jetonomy" footer text.
email_logo_url Logo shown at the top of notification and digest emails.

Tip: header_logo_url and logo_url are deliberately separate. Set header_logo_url for the on-page logo and logo_url for the share-card / fallback image - they are often different sizes and aspect ratios.

REST API

White Label exposes its settings under jetonomy/v1:

Method Endpoint Description
GET /settings/white-label Read the current white-label settings
PATCH /settings/white-label Save white-label settings

Both routes require manage_options. See the REST API reference for full payloads.

Upgrading From Older Versions

On Jetonomy 1.4.1 and newer every White Label setting applies on every surface - the header logo, footer text, email accent colour, email logos, the sidebar sign-in card, and the admin footer all rebrand on each render and send. On older versions the Branding settings saved but did not always take effect; update both the free and Pro plugins together so all branding applies.

What's Next?

Bring large language models into your community for smarter spam detection, auto-moderation, reply suggestions, and thread summaries.

AI Integration →

Bring large language models into your community - for smarter spam detection, auto-moderation, reply suggestions, and thread summaries - without sending data to a third party unless you want to.

PRO - This feature requires Jetonomy Pro.

The AI Integration settings tab: provider activation (OpenAI, Anthropic, Ollama, Custom), the default-provider fallback chain, per-feature toggles for spam detection, content moderation, reply suggestions and thread summaries, the live usage dashboard, member usage limits, and the monthly spend cap

What You Will Learn

  • How the AI extension is organized (providers + features)
  • Which AI providers Jetonomy supports, including self-hosted Ollama
  • How to enable AI-powered spam detection, content moderation, reply suggestions, and thread summaries
  • Where to monitor token usage and cost
  • How to keep community content private by running models locally

Why AI Matters for Communities

Moderation is the single biggest burden on community owners. Every post and every reply is a potential spam attempt, abuse report, or duplicate question. At 50 topics a day, a human moderator can keep up. At 500, they cannot.

The Jetonomy Pro AI extension gives your community a language model that reads every new post and every new reply, flags the ones that need a human, and leaves the rest alone. It does not replace your moderators - it filters what they see so they can focus on judgment calls instead of the obvious cases.

Supported Providers

Jetonomy's AI layer is built on a pluggable adapter pattern. Four providers ship out of the box:

Provider Hosted by Best for
Ollama You - runs on your own server Privacy-sensitive communities, GDPR, full data control
OpenAI OpenAI Fastest time to value, best general quality
Anthropic Anthropic (Claude) High-quality moderation, long context windows
Custom You Self-hosted vLLM, LM Studio, or any OpenAI-compatible endpoint

You can register more than one provider and assign different features to different providers - for example, run moderation through Ollama locally while using OpenAI only for summaries of long threads.

Tip: If you already run Ollama on the same server as WordPress, the AI extension uses it over localhost - no network hop, no API key, no data leaves your machine.

Multi-Provider Fallback Chain

Every AI feature can be assigned a primary provider plus an optional fallback chain. If the primary provider errors mid-request (rate limit, timeout, 5xx), Jetonomy automatically retries the next provider in the chain before surfacing an error to the admin.

Typical setup: Ollama (primary) → OpenAI (fallback) → Anthropic (second fallback). You get privacy-first moderation with a safety net for the moments when the local model is restarting or overloaded.

The chain is configured per feature at Jetonomy → Settings → AI Integration → Advanced. Requests that succeed via the fallback chain are logged with a fallback_used flag so you can spot providers that are flapping.

Monthly Spend Caps

Every cloud provider (OpenAI, Anthropic, Custom) can be given a monthly spend cap in USD. Once usage crosses the cap in the current billing window, Jetonomy stops dispatching requests to that provider and surfaces a clear admin notice explaining what happened and when the cap resets.

Spend caps protect you from a runaway loop - a broken summary trigger, a spam wave that hits the reply-suggestion endpoint - draining your account. Self-hosted Ollama has no cap UI because it has no per-request cost.

Caps are evaluated against the usage log (wp_jt_pro_ai_usage), not the provider's invoice, so the cap reacts immediately. Expect a 2 - 3% variance versus your invoice at the end of the month because Jetonomy's estimate uses published token rates.

AI-Powered Features

Each feature can be toggled independently from Jetonomy → Settings → AI Integration.

How Review Runs

AI review never sits between a member and the publish button. Posts and replies appear the instant they are submitted, with zero AI latency, so posting always stays fast. A background job then reviews the new content in batches on a schedule.

  • Batched and scheduled. A recurring job (jetonomy_pro_ai_batch_review) sweeps everything posted since its last run and classifies many items in a single model call - the spam and content-moderation checks share one pass. On a quiet site the sweep exits without touching the API at all.
  • Runs every 5 minutes by default. The interval is filterable via jetonomy_pro_ai_review_interval (default 300 seconds, floored at 60 seconds).
  • Trusted members are skipped. Staff and members at or above a trust level are never sent to the model. The exemption threshold is filterable via jetonomy_pro_ai_review_exempt_trust_level (default trust level 3).
  • Throughput controls. Each sweep inspects up to jetonomy_pro_ai_review_batch_cap new posts and replies (default 60), classifying jetonomy_pro_ai_review_chunk_size items per model call (default 12). A failed model call is retried on the next cycle rather than dropped.

Both Spam Detection and Content Moderation below run together in this same background pass.

Spam Detection

Replaces the free plugin's pattern-based spam detector with a model-driven classifier. Posts and replies publish instantly - the AI is never in the posting path. The background reviewer then scores each new post and reply for spam probability and moves anything above the threshold to the moderation queue. Content below the threshold stays published as normal.

Works alongside Akismet and trust-level rate limits - AI spam detection is the third layer, not a replacement.

Content Moderation

Flags content that breaks rules you describe in plain English. You write a few sentences - "no political attacks, no personal insults, no harassment" - and the background reviewer reads each new post and reply against that policy.

Violations are sent to the moderation queue with a reason the model generated. Your human moderators make the final call, but they see a pre-filled explanation instead of a blank flag.

Moderation presets - Four tuned presets ship out of the box so you do not have to write a policy from scratch:

Preset Tuned for
Community Forum General discussion boards. Flags spam, harassment, obvious off-topic. Permissive on mild profanity and opinion-based disagreement.
Support Desk Product and service support. Flags abusive language toward staff, solicitation, and off-topic promotion. Permissive on frustration.
Kids Safe Under-13 communities. Aggressive filter - any profanity, sexual language, personal contact requests, or outside links flagged.
Academic University and research communities. Flags plagiarism indicators, academic dishonesty keywords, and personal attacks. Permissive on technical debate.

Pick a preset as your starting point and customize from there. The preset fills the policy text field - you edit it - and the model uses your edited copy.

Reply Suggestions

When a member is composing a reply, Jetonomy can ask the model for a draft based on the topic context. The member can accept it, edit it, or ignore it. Draft replies are never sent without human approval.

Great for knowledge-base communities where most answers follow a pattern.

Thread Summaries

On long threads (30+ replies), Jetonomy can generate a short summary pinned at the top of the topic. New visitors read the summary instead of scrolling through every back-and-forth. Summaries regenerate when new replies meaningfully change the conversation.

Cached in the wp_jt_pro_ai_cache table so each summary is generated once per content state.

Enabling AI Integration

  1. Go to Jetonomy → Extensions and enable AI.
  2. Open Jetonomy → Settings → AI Integration.
  3. Choose a provider. For Ollama, enter the base URL (usually http://localhost:11434) and the model name (for example, llama3.1:8b).
  4. (Optional) Add a fallback provider and set a monthly spend cap at AI Integration → Advanced.
  5. Turn on the individual features you want - Spam Detection, Content Moderation, Reply Suggestions, or Thread Summaries. Pick a Content Moderation preset if you enable moderation.
  6. Save.

Each feature has its own Enable switch and an optional Provider Override, so you can, for example, run moderation locally on Ollama while sending summaries to OpenAI - see the per-feature toggles in the AI Integration settings tab above.

Usage Tracking and Cost

Every AI request is logged to the wp_jt_pro_ai_usage table with the model used, token counts, latency, and estimated cost.

AI Usage dashboard widget - A dedicated widget appears on the main Jetonomy admin dashboard as soon as at least one provider is connected. It shows at a glance:

  • Requests today and this month
  • Token usage by feature
  • Estimated spend by provider (and percentage of the monthly cap consumed)
  • Average response time
  • Error rate and fallback-chain activations

The AI Usage widget on the main Jetonomy admin dashboard, showing requests today and this month, token usage by feature, estimated spend by provider with the share of the monthly cap consumed, average response time, and error rate

A detailed breakdown lives at Jetonomy → Settings → AI Integration → Usage with per-day charts, per-feature filtering, and CSV export.

If a feature starts running hot (a new spam wave, an unexpected summary loop), you see it in the dashboard and can pause that feature without disabling the whole extension. The spend cap will also pause the provider automatically if the runaway is expensive enough to cross the cap.

Privacy and Self-Hosting

For communities that cannot send member content to a third-party API - legal, health, financial, enterprise internal - run Ollama on the same server. Jetonomy talks to the model over localhost only. No external network calls, no API keys, no data leaves your machine.

The wp_jt_pro_ai_log audit table records every model decision (feature, object, confidence, action taken) so you have a permanent record of what the AI did and why - useful for compliance reviews.

REST API

The AI extension registers three endpoints under jetonomy/v1:

Method Endpoint Description
GET /ai/usage Aggregated usage metrics for a date range
GET /ai/usage/summary Rolled-up usage summary for a date range
POST /ai/suggest-reply Request a reply suggestion - pass post_id in the JSON body

The two usage endpoints require an administrator (manage_options). /ai/suggest-reply requires any logged-in member and is rate-limited.

What's Next?

Give every Pro space its own SEO controls - custom meta titles, Open Graph images, schema, and sitemap rules.

SEO Pro →

Give every space its own meta titles, Open Graph images, Twitter Cards, schema markup, and sitemap rules - without touching your site-wide SEO plugin.

PRO - This feature requires Jetonomy Pro.

The SEO tab inside a space's settings panel, showing the meta title, meta description, and Open Graph image fields

What You Will Learn

  • Why community SEO is different from page/post SEO
  • How to set per-space meta titles and descriptions
  • How to control Open Graph and Twitter Card previews per space
  • How Jetonomy outputs Schema.org structured data automatically
  • How to include or exclude spaces from your sitemap
  • How to customize robots directives and canonical URLs

Why Community SEO Is Different

Your posts and pages have one author, one publish date, and one body. A forum topic has dozens of authors, evolves over time, and the most recent reply is often the most important part. A Q&A thread is really a QAPage with an acceptedAnswer, not a blog post. A sitemap that treats every reply as a top-level URL pollutes your search index.

The free Jetonomy plugin already outputs baseline SEO - canonical URLs, Open Graph tags, Schema.org, and a sitemap provider for spaces and posts. SEO Pro is for when you need to override the defaults on a per-space basis, because different spaces have different audiences, different goals, and different search intent.

Per-Space Meta Title and Description

Every space gets a dedicated SEO tab in its settings panel. Inside that tab:

  • Meta Title - defaults to the space name. Override for search engines only (the on-site title stays unchanged).
  • Meta Description - defaults to the space description. Override with a keyword-rich summary written for search snippets.
  • Meta Keywords - legacy field, not used by Google but some vertical engines still read it. Optional.

Both meta title and description support template tokens: {space_name}, {site_name}, {space_count}, {post_count}. For example, a meta title template of {space_name} - {post_count} discussions | {site_name} produces Python Help - 2,481 discussions | MyForum automatically.

Open Graph and Twitter Cards

SEO Pro lets you upload a custom Open Graph image per space - a 1200x630 image used when the space URL is shared on Facebook, LinkedIn, and Slack. It also lets you override:

  • Open Graph title (defaults to meta title)
  • Open Graph description (defaults to meta description)
  • Twitter Card type (summary or summary_large_image)
  • Twitter Card image

If you skip these fields, Jetonomy falls back to the meta title/description and uses the default OG image from your main settings.

Schema.org Structured Data

For every space and every post, Jetonomy Pro emits JSON-LD structured data:

  • Space pages - DiscussionForumPosting with post count and date
  • Topic pages (Forum) - DiscussionForumPosting with author, date, and answer count
  • Topic pages (Q&A) - QAPage with acceptedAnswer (if marked) and suggestedAnswer entries
  • Topic pages (Ideas) - CreativeWork with about (the idea)
  • User profiles - Person with memberOf the space list
  • Breadcrumbs - BreadcrumbList on every community page

You do not need to configure any of this. Enabling SEO Pro turns it on automatically. The free plugin emits a subset of these (basic DiscussionForumPosting and BreadcrumbList) - SEO Pro adds the richer types.

Sitemap Controls

Jetonomy core registers a sitemap provider for spaces and posts. SEO Pro adds:

  • Include / exclude per space - mark a private support space as excluded from the sitemap with one click
  • Priority per space - boost your flagship space above others in the sitemap XML
  • Change frequency per space - hint at how often a space updates (always, hourly, daily, weekly, monthly)
  • Maximum URLs per sitemap - split large sitemaps into chunks that search engines can crawl without timing out

All sitemap changes take effect on the next request - no flush needed.

Robots Directives and Canonical URLs

  • Robots meta per space - set noindex, nofollow, noarchive, or any combination on a space-by-space basis. Useful for staff-only spaces that should not appear in search results.
  • Custom canonical URL - override the default canonical URL on a space or topic. Useful when you syndicate content from an external blog and want Google to credit the original.
  • robots.txt directives - add SEO Pro specific rules to your site's virtual robots.txt without touching a file.

Enabling SEO Pro

  1. Go to Jetonomy → Extensions and enable SEO Pro.
  2. Open any space in Jetonomy → Spaces.
  3. Click the SEO tab inside the space settings panel.
  4. Fill in the fields you want to override. Leave the rest blank to inherit the site defaults.
  5. Save.

No site-wide settings page - SEO Pro is intentionally per-space, so you do not accidentally change defaults that other spaces depend on.

REST API

Method Endpoint Description
GET /spaces/{id}/seo Get the current SEO settings for a space
PATCH /spaces/{id}/seo Update the SEO settings for a space

Both endpoints require the manage_jetonomy capability or space-admin role.

Compatibility With Site-Wide SEO Plugins

SEO Pro does not replace Yoast, Rank Math, or All in One SEO. It handles the community area only - URLs under /community/ - and leaves your blog, pages, and WooCommerce products entirely alone. If a site-wide SEO plugin already writes og:title for a community URL, SEO Pro's tags take precedence on community pages.

What's Next?

Feature an important post at the top of every space across your whole community.

Site Announcements →

Feature an important post at the top of every space across your whole community.

PRO - This feature requires Jetonomy Pro.

What You Will Learn

  • How to enable Site Announcements
  • What a community announcement is, and how it differs from pinning a topic in a space
  • Who is allowed to create announcements
  • How to pin a post to the community and where it appears
  • How many announcements you can have at once
  • How to remove an announcement

Enabling Site Announcements

Site Announcements is one of the Pro extensions, enabled the same way as the others:

  1. Go to Jetonomy → Extensions in your WordPress admin.
  2. Find Site Announcements and click Enable.
  3. A Pin to community button appears in the action bar of every post for administrators.

Announcements vs Space Pinning

Jetonomy has two separate "pin" tools for two different jobs:

Space pin ("Pin") Community announcement ("Pin to community")
Scope Top of one space only Top of every space across the community
Who can use it Space moderators and admins Administrators only
Badge shown green Pinned green Announcement
Where to find it The topic's ... menu A Pin to community button on the topic

Use a space pin for a "start here" thread inside one forum. Use a community announcement for something everyone should see no matter which space they are browsing - a maintenance notice, a major release, or community-wide rules. Space pinning is covered in Topic Management.

Who Can Create Announcements

Community announcements are administrator-only. The control is gated by the manage_options and jetonomy_manage_spaces capabilities, and jetonomy_manage_spaces is granted only to the Administrator role by default. Moderators who can pin topics within their own space cannot create site-wide announcements - that power stays with admins, because an announcement affects every space.

Pinning a Post to the Community

  1. Open the post you want to feature.
  2. In the action bar below the post, click Pin to community.

The post is immediately featured across the community. The button changes to Unpin from community, and an Announcement badge appears on the post.

Where Announcements Appear

A community announcement is shown in two places:

  • At the top of every space's listing - above that space's own topics, with an Announcement badge, so members see it wherever they browse.
  • On the post's own header - with the same Announcement badge.

A post can be both space-pinned and a community announcement at the same time; in that case it shows both the Pinned and Announcement badges, which is expected.

The Announcement Limit

You can have up to 5 community announcements at once. When the limit is reached, pinning another post returns "You can only pin 5 announcements at a time. Unpin one first." Keeping the set small protects the value of the slot - if everything is an announcement, nothing is.

Removing an Announcement

Open the post and click Unpin from community in the action bar. The post returns to its normal position everywhere and the Announcement badge is removed. This does not delete or unpin the post within its own space - if it was also space-pinned, that pin remains.

REST API

Site Announcements registers these endpoints under jetonomy-pro/v1:

Method Endpoint Description
GET /site-announcements List the current set of pinned posts and the pin limit
POST /site-announcements/{id} Pin a post to the community
DELETE /site-announcements/{id} Unpin a post from the community

{id} is the numeric post ID. Pinning and unpinning require the manage_options or jetonomy_manage_spaces capability (Administrator by default); the same capability is required to list the current pins. See the REST API reference for full payloads.

The three routes above are the administrator management surface: they read and write the raw pin option, so they are admin-gated.

Mobile / in-app banner

Mobile and in-app clients read announcements through a separate member-facing route, served under the free jetonomy/v1 namespace alongside the other app read routes (not jetonomy-pro/v1):

Method Endpoint Description
GET /announcements/active The active announcements the current member is allowed to see

It powers an in-app banner of the currently pinned announcements. The read is visibility-aware - a member only receives announcements on posts they can access. The response returns a data array plus a meta.total count:

GET /wp-json/jetonomy/v1/announcements/active
{
  "data": [
    {
      "id": 128,
      "title": "Scheduled maintenance this weekend",
      "space_id": 4,
      "url": "https://example.com/community/s/general/t/scheduled-maintenance/",
      "created_at": "2026-06-01 09:30:00"
    }
  ],
  "meta": { "total": 1 }
}

Each item carries the announcement post id, its title, the space_id it lives in, a url deep link the client opens, and the created_at timestamp.

What's Next?

Let members post topics and replies without showing their name, with an audited admin-only reveal.

Anonymous Posting →

Let members post topics and replies without showing their name or avatar to other members, with an audited admin-only reveal.

PRO - This feature requires Jetonomy Pro.

What You Will Learn

  • How to enable Anonymous Posting site-wide and allow it in individual spaces
  • What members see when they post or reply anonymously
  • Why the real author is still tracked behind the scenes
  • How administrators reveal the real author, and how every reveal is logged
  • How to use the REST API reveal endpoint

Why Anonymous Posting Matters

Some conversations only happen if the poster's name is hidden - a support question about a sensitive personal issue, honest feedback about a manager, or a first post in a topic the member is nervous about. Anonymous Posting lets you open that door in the spaces where it fits, while keeping every anonymous author accountable behind the scenes.

Enabling Anonymous Posting

Anonymous posting needs two switches, both on. It never appears with only one of them set.

  1. Site-wide: Go to Jetonomy → Extensions, find Anonymous Posting, and click Enable. This is the global master switch - there is no separate settings screen for it.
  2. Per space: Open the space, go to Edit → Anonymous, tick Allow anonymous posts in this space, and save.

Turn the space option on only where it fits - a support, feedback, or sensitive-topics space, for example. Leave it off everywhere else. A space where the option is off never shows the anonymous controls, even while the extension is enabled globally.

What Members See

In a space that allows it, members get:

  • New topic: a Post anonymously checkbox in the composer fields.
  • Reply: a compact anonymity toggle (mask icon) in the reply toolbar, which highlights when active.

When a member submits with either one on, their post or reply shows as Anonymous to everyone else - in the feed, on the topic, in replies, in search, in the RSS feed, in notifications, on profiles, and in the mobile app. It uses the same author-resolution path everywhere, so no surface can leak the real name by accident.

Server-Side Enforcement

The anonymity flag is never trusted from the client alone. Every post and reply create request is re-validated server-side against both gates (the global switch and the space opt-in) before it is allowed to save as anonymous - so a member cannot force anonymity in a space that has not opted in, even by editing the request.

Revealing the Real Author

Only site administrators can reveal who is behind an anonymous post or reply - space moderators cannot. An admin uses the Reveal author button on the post or reply. The real name is shown only for that explicit action; ordinary admin browsing still shows "Anonymous."

Every reveal is written to the activity log, recording who revealed it, which item, and when - so reveals stay accountable. Use this sparingly, for abuse investigations rather than routine browsing.

REST API

Anonymous Posting registers this endpoint under jetonomy/v1:

Method Endpoint Description
POST /anonymous/reveal Reveal the real author of an anonymous post or reply

POST /anonymous/reveal - body

{
  "object_type": "post",
  "object_id": 128
}

object_type is post or reply. The route requires the manage_options capability (administrators by default) and returns 403 if the caller lacks it, or 404 if the object is not actually anonymous. A successful call returns the real author's id and name and writes the reveal to the activity log. See the REST API reference for full payloads.

What's Next?

Let members attach images, PDFs, and documents to topics and replies.

File Attachments →

Let members attach images, PDFs, and documents to topics and replies, with server-rendered preview cards and a lazy-loaded inline PDF viewer.

PRO - Attaching new files (the composer), the lightbox, the inline PDF viewer, and file type/size limits require Jetonomy Pro. As of 1.8.0, viewing attachments that already exist on a post does not - the free Jetonomy plugin displays them on its own, and keeps displaying them even if Pro is later deactivated.

What You Will Learn

  • What the free plugin shows on its own, and what Pro adds on top
  • How to enable File Attachments
  • How to configure allowed file types, max file size, and max files per post
  • What members see for images, PDFs, and documents
  • How attachments are validated, cleaned up, and cascade-deleted
  • How to use the REST API to attach, detach, download, and batch-read attachments

Why File Attachments Matter

A screenshot, log file, or spec document often explains a problem faster than a paragraph of text. File Attachments lets members back up their posts and replies with real files, directly in the thread, instead of pasting external links that rot over time.

Free vs. Pro

The free Jetonomy plugin already renders any attachment that lands on a post or reply - through an import, the mobile app, or the REST API - even with Pro off. Enabling this Pro extension adds the ability to attach new files plus a richer display:

Free With File Attachments (Pro)
Display existing attachments Yes Yes
Images Inline thumbnail Inline thumbnail + click-to-enlarge lightbox
PDFs / documents Download chip (name, size, type) Download chip, plus a first-page preview and inline viewer for PDFs
Attach a new file from the composer No Yes
Configurable allowed types, max size, max count - Yes

Enabling File Attachments

  1. Go to Jetonomy → Extensions, find File Attachments, and click Enable.
  2. The composer gains an Attach files control - a full Attach files button on the new-topic form, and a compact paperclip icon in the reply toolbar.

Configuring Allowed Types, Size, and Count

Go to Jetonomy → Settings → Attachments to control:

Setting Default Description
Allowed file types jpg, jpeg, png, gif, webp, pdf, docx, xlsx, pptx, odt, txt, csv Grouped as Images and Documents; toggle each type independently
Max file size 10 MB Per file
Max files per post/reply 5 Enforced on both the composer and the REST endpoint

SVG is never available, because it can carry executable scripts. Every upload is re-validated against this allow-list on the server - the file extension and the actual file content must both match an allowed type - so the settings are a real security boundary, not just a UI restriction.

What Members See

Attached files render as cards under the post or reply. The base card - image thumbnail or a download chip with file name, size, and type - is rendered by the free plugin. With File Attachments enabled, Pro upgrades the card:

  • Images show an inline thumbnail; with Pro, click it to enlarge in a lightbox.
  • PDFs show a download chip by default; with Pro, the card instead shows a first-page preview with filename · pages · size and an Open viewer button that opens the file inline, with page navigation and zoom, or in a new tab. The thumbnail needs your host's Imagick and Ghostscript support - if unavailable, the card falls back to the document icon and the file still opens and downloads normally.
  • Documents (DOCX, XLSX, PPTX, ODT, TXT, CSV) show the same download chip with the file type and size, with or without Pro.

The cards are server-rendered on every page load, so they show correctly even with JavaScript disabled. The PDF viewer library itself is only downloaded the first time a member opens a PDF - never on a normal page load - so it does not slow down browsing.

If You Deactivate Pro

Attachment link records live in the free plugin as of 1.8.0. Deactivating or uninstalling Jetonomy Pro does not remove or hide attachments already on a post or reply - the free plugin keeps rendering them exactly as described in "Free vs. Pro" above. You only lose the composer's Attach files control, the lightbox, the inline PDF viewer, and the configurable limits until Pro is reactivated.

Data Lifecycle

  • Removing an attachment detaches it from the post or reply; the underlying uploaded file is left alone in case it is still referenced elsewhere.
  • Deleting a topic or reply removes its attachment links automatically.
  • Orphan cleanup runs once a day: it drops link rows whose underlying file was removed some other way, and deletes community uploads that were never linked to any post or reply within 24 hours of being uploaded. This sweep is scoped strictly to files uploaded through the community composer, so it never touches the site owner's own WordPress media library. Cleanup only removes files this plugin created, never another forum plugin's media, so a migration can never lose files.

REST API

File Attachments registers these endpoints under jetonomy-pro/v1:

Method Endpoint Description
POST /attachments Link an already-uploaded attachment to a post or reply
DELETE /attachments/{id} Detach an attachment from its post or reply
GET /attachments/{id}/download Download the file
GET /attachments/batch Batch-read attachments for many posts or replies in one call

{id} on DELETE and the download route is the attachment link ID, not the underlying WordPress attachment ID. Attaching requires the jetonomy_create_posts, jetonomy_create_replies, or jetonomy_upload_media capability and ownership of the uploaded file; detaching requires the same ownership or the moderate_comments capability. The batch route requires jetonomy_manage_settings or moderate_comments and is meant for admin/moderator listing, not the member-facing composer.

POST /attachments - body

{
  "object_type": "post",
  "object_id": 512,
  "attachment_id": 4820,
  "sort": 0
}

attachment_id is the WordPress attachment ID returned by POST /jetonomy/v1/media. The file is re-validated against the Attachments allow-list on attach, and the per-object file cap from the settings page is enforced server-side.

Posts and replies also carry an attachments[] array directly on their normal GET/list responses, so most clients never need to call these routes directly except to attach or detach a file. See the REST API reference for full payloads.

What's Next?

You have now seen every Pro feature. Return to the Pro getting-started guide to choose which extensions to enable for your community.

Integrations

Connect with membership plugins, themes, and third-party services.

Jetonomy connects to the membership, course, and community plugins you already run, so a member's access to a discussion space follows their subscription, course enrolment, or group membership automatically. This page is the map of every integration - what each one does, and whether it is in the free plugin or needs Jetonomy Pro.

What You Will Learn

  • Which integrations gate space access (membership and LMS) versus connect two communities (coexistence)
  • Which integrations are in the free plugin and which require Jetonomy Pro
  • Where to go next for each one

Access-gating integrations

These connect an external "who has paid / who is enrolled" system to Jetonomy's Access Rules, so members are added to (and removed from) spaces automatically. They all follow the same Access Rules flow, walked through step by step in the MemberPress guide.

Integration Free or Pro What it gates a space by
MemberPress Free MemberPress membership level
Paid Memberships Pro Free PMPro subscription level
WooCommerce Pro WooCommerce Memberships plan or Subscription product
Restrict Content Pro Pro RCP subscription level
LearnDash Pro LearnDash course or group enrolment
Tutor LMS Pro Tutor course enrolment
LifterLMS Pro LifterLMS course or membership enrolment
Sensei LMS Pro Sensei course enrolment
MasterStudy LMS Pro MasterStudy course enrolment
Learnomy Pro Learnomy course or membership plan
WP Fusion Pro WP Fusion CRM tag
SureMembers Pro SureMembers access group

WP Fusion and SureMembers setup. These two gate a space from their own tab on the space editor rather than the shared Access Rules picker, and each links tags or access groups back to the source system on join. See the WP Fusion and SureMembers guides for the full walkthrough.

Tip: Access Rules from different integrations stack. You can gate one space to "MemberPress VIP or a specific WooCommerce product or a LearnDash course" - a member who matches any rule gets in.

Coexistence integrations

These do not gate access by purchase. Instead they make Jetonomy and another community plugin feel like one product - shared members, paired spaces, cross-links, and (where supported) activity broadcast. All are in the free plugin and turn on automatically when the other plugin is active.

Integration Free or Pro What it connects
BuddyNext Free Shares BuddyNext's design tokens and lets BuddyNext own the page header and navigation
BuddyPress Free Pairs BuddyPress groups with forum spaces; two-way member sync, group Forum tab, activity broadcast
FluentCommunity Free Pairs FluentCommunity spaces with Jetonomy spaces; add-only member sync, cross-profile links, activity broadcast

Theme compatibility

Integration Free or Pro What it does
Theme Compatibility Free Adapts Jetonomy to any WordPress theme via CSS custom properties, with full template overrides

Where to Start

  • Running a paid community on MemberPress or Paid Memberships Pro? Start with MemberPress - it is the canonical Access Rules walkthrough every other gating guide links back to.
  • Selling courses? Go to the lead LMS guide, LearnDash, then the matching guide for your LMS.
  • Already on BuddyPress or FluentCommunity? Open the BuddyPress or FluentCommunity guide - nothing to configure to switch the integration on.

Connect MemberPress membership levels to Jetonomy spaces - so paying members automatically land in the right discussion areas the moment their subscription activates.

Available in Jetonomy free. The MemberPress and Paid Memberships Pro adapters ship in the free plugin - you do not need Jetonomy Pro to gate spaces by these two membership plugins. (WooCommerce, Restrict Content Pro, and all LMS integrations require Jetonomy Pro.)

Jetonomy admin settings panel for configuring integrations

What You Will Learn

  • How Jetonomy detects and communicates with MemberPress
  • How to gate a space by membership level using Access Rules
  • What happens when a membership activates or expires
  • How to test the integration before going live

How Detection Works

Jetonomy checks for MemberPress automatically on every page load. No configuration needed. When MemberPress is active, the MemberPress adapter registers itself with Jetonomy's Adapter Registry and enables the Access Rules UI inside each space's settings.

Note: If you activate MemberPress after Jetonomy, navigate to Jetonomy → Settings and save once. This triggers adapter re-registration.

Setting Up an Access Rule

This is the standard Access Rules flow that every membership and LMS integration in this section follows. The other integration guides link back here for the full walkthrough.

Jetonomy Access Rules tab showing a saved membership rule with its Type, Value, Grants, and Space Role columns

  1. Go to Jetonomy → Spaces and open the space you want to gate.
  2. Click the Access Rules tab in the space settings panel.
  3. Set Rule Type to your MemberPress level (membership levels appear in the dropdown once MemberPress is active).
  4. Pick the membership level in the Value field.
  5. Choose what the rule Grants - Read, Participate, or Full (see below).
  6. Choose the Space Role members get when they match - Viewer, Member, Moderator, or Admin (see below).
  7. Click Add Rule. The rule appears in the table below the form.

Members who hold the selected level gain access to this space at the level you chose. Members without it see the space as locked (or hidden, depending on your space visibility setting).

Tip: Add more than one rule if you want to grant access for more than one membership level. Rules are evaluated top to bottom by priority, and a member passes on the first rule they match.

Grants and Space Role

Every Access Rule has two settings that decide what a matching member can do. They are the same across all integrations:

Grants - how much of the space the member can use:

Grants What the member can do
Read View topics and replies, but not post or reply
Participate Read, post topics, and reply (the usual choice for a course or paid space)
Full Participate plus the space-management abilities tied to their Space Role

Space Role - the role the member is given inside this one space:

Space Role Meaning
Viewer Read-only presence in the space
Member A regular participating member (the usual choice)
Moderator Can moderate content in this space
Admin Can manage this space's settings and members

For most gated spaces - a paid membership or a course community - set Grants: Participate and Space Role: Member. Use Moderator or Admin only when you specifically want a membership tier to run the space.

Note: There is no separate "Grant vs Revoke" switch. A rule always grants the access you choose to members who match it; members who match no rule simply do not get in. To take a level's access away, delete its rule.

Auto-Join and Auto-Leave

When a MemberPress membership activates, Jetonomy automatically adds the member to any spaces whose Access Rules grant that level. They receive a welcome notification in the community.

When a membership expires, cancels, or is paused, Jetonomy fires jetonomy_membership_deactivated and removes the member from any spaces gated exclusively to that level. Their posts and replies remain intact.

This is handled by the MemberPress_Adapter class. It hooks mepr-txn-status-complete for activation, and mepr-txn-status-refunded, mepr-txn-expired, and mepr_subscription_transition_status for deactivation.

Visibility Behavior

Space Visibility Non-member sees...
Public Space listed, content visible, locked from posting
Private Space listed with lock icon, content hidden
Hidden Space not listed at all

Developer Hook

Both membership events fire the Jetonomy standard hooks you can use in your own code:

// Fires when a MemberPress membership activates.
add_action( 'jetonomy_membership_activated', function( int $user_id, string $level_id, string $adapter ) {
    // $adapter will be 'memberpress'
    if ( 'memberpress' === $adapter ) {
        // Custom logic here.
    }
}, 10, 3 );

Troubleshooting

Access rules dropdown is empty - MemberPress may not be active. Check Plugins → Installed Plugins and confirm MemberPress is activated.

Member not joining on activation - Ensure the membership level ID in the Access Rule exactly matches the level in MemberPress. Level IDs are numeric; check the MemberPress level edit URL for the ID.

Member still has access after expiry - Check whether the member holds a second membership level that also grants access to the space.

What's Next?

Learn how to gate spaces using Paid Memberships Pro, which follows the same pattern.

Paid Memberships Pro Integration →

Gate Jetonomy spaces by Paid Memberships Pro subscription level - with automatic access granted on activation and revoked on cancellation or expiry.

Available in Jetonomy free. The Paid Memberships Pro adapter ships in the free plugin - Jetonomy Pro is not required to gate spaces by PMPro levels.

Jetonomy admin settings showing integration configuration options

What You Will Learn

  • How Jetonomy detects Paid Memberships Pro (PMPro)
  • How to set up an Access Rule tied to a PMPro level
  • What triggers the auto-join and auto-leave behavior
  • The hook names you can use for custom logic

How Detection Works

Jetonomy detects PMPro automatically when the plugin is active. The PMPro adapter registers with Jetonomy's Adapter Registry and unlocks the Access Rules tab in space settings. No manual connection step is required.

Note: If you activate PMPro after Jetonomy is already running, go to Jetonomy → Settings and save the page once to trigger adapter re-registration.

Setting Up an Access Rule

  1. Go to Jetonomy → Spaces and open the space you want to gate.
  2. Open the Access Rules tab in the space settings panel.
  3. Set Rule Type to your Paid Memberships Pro level (PMPro levels appear in the dropdown once PMPro is active).
  4. Pick the PMPro level in the Value field.
  5. Set Grants to Participate and Space Role to Member for a standard gated space.
  6. Click Add Rule. The rule appears in the table below the form.

Members who hold the selected PMPro level gain access to the space immediately. You can stack multiple rules - access is granted if the member matches any rule.

Tip: For a full explanation of the Grants (Read / Participate / Full) and Space Role (Viewer / Member / Moderator / Admin) choices, see Grants and Space Role in the MemberPress guide - they work the same for every integration.

Auto-Join and Auto-Leave

On activation - when a member's PMPro level activates, Jetonomy adds them to any spaces where that level is in an Access Rule. The hook jetonomy_membership_activated fires with $adapter = 'pmpro'.

On cancellation or expiry - when a PMPro level expires or is manually cancelled, Jetonomy removes the member from gated spaces. The hook jetonomy_membership_deactivated fires with $adapter = 'pmpro'.

The adapter hooks into PMPro's pmpro_after_change_membership_level action to detect both events.

Visibility Behavior

Space Visibility Non-member sees...
Public Space listed, content readable, posting locked
Private Space listed with lock icon, content hidden
Hidden Space not listed at all

Developer Hook

// Fires when a PMPro membership deactivates.
add_action( 'jetonomy_membership_deactivated', function( int $user_id, string $level_id, string $adapter ) {
    if ( 'pmpro' === $adapter ) {
        // Remove user from any related external system.
        my_crm_remove_access( $user_id, $level_id );
    }
}, 10, 3 );

Troubleshooting

Level dropdown is empty in Access Rules - Confirm PMPro is active and that you have at least one membership level created in Memberships → Membership Levels.

Member not removed on cancellation - PMPro has several cancellation states (expired, admin-cancelled, non-renewing). Jetonomy listens to all of them via pmpro_after_change_membership_level where the new level is 0.

Multiple levels, unexpected access - If a user holds more than one PMPro level, Jetonomy evaluates all rules. Access persists as long as any single rule still matches an active level.

What's Next?

Gate spaces using a WooCommerce product purchase or subscription - available in Jetonomy Pro.

WooCommerce Integration →

Gate Jetonomy spaces by WooCommerce Membership plan or active WooCommerce Subscription - so customers get access to discussion areas as soon as their membership or subscription becomes active.

The Jetonomy admin settings screen

PRO - This feature requires Jetonomy Pro.

What You Will Learn

  • Which WooCommerce levels Jetonomy Pro supports as access gates
  • How to set up an Access Rule tied to a membership plan or subscription
  • How access activates and revokes on membership and subscription status changes
  • How to combine WooCommerce gates with membership-level gates

Supported Gate Types

The WooCommerce adapter activates only when WooCommerce is active and either WooCommerce Memberships or WooCommerce Subscriptions is also active. What you can select as a gate is a Membership plan or a Subscription product - not a plain Simple or Variable product.

Gate Type Supported Notes
WooCommerce Memberships plan Yes Access tracks the membership status (active/paused/cancelled)
WooCommerce Subscription product Yes Access active while the subscription is active; revoked on hold, cancel, or expiry
Variable-subscription product Yes Selectable like any other subscription product
Plain Simple product No Use a WooCommerce Memberships plan tied to that product instead
Plain Variable product No Use a WooCommerce Memberships plan instead
Grouped product No Gate via a Membership plan instead

Note: Either WooCommerce Memberships or WooCommerce Subscriptions (both WooCommerce.com extensions) is required. WooCommerce on its own does not enable space gating - the adapter stays inactive until one of the two is present.

Setting Up an Access Rule

  1. Install and activate Jetonomy Pro, ensure WooCommerce is active, and ensure WooCommerce Memberships or WooCommerce Subscriptions is active.
  2. Go to Jetonomy → Spaces and open the space you want to gate.
  3. Click the Access Rules tab.
  4. Set Rule Type to WooCommerce.
  5. Pick the Membership plan or Subscription product in the Value field.
  6. Set Grants to Participate and Space Role to Member for a standard gated space.
  7. Click Add Rule.

The Access Rule takes effect immediately. Members who already hold the membership or active subscription are granted access in the background within a few seconds of saving.

Tip: You can add multiple WooCommerce rules to a single space. Access is granted if the member matches any one of the listed memberships or subscriptions. For what the Grants and Space Role fields mean, see Grants and Space Role.

Auto-Activate

Jetonomy Pro listens to WooCommerce Memberships and WooCommerce Subscriptions status changes - not to raw order status transitions.

For WooCommerce Memberships, when a membership becomes active Jetonomy Pro adds the member to any spaces that grant access on that plan and sends a welcome notification.

For WooCommerce Subscriptions, access tracks the subscription status:

Subscription Status Access
Active Granted
On-hold Revoked
Cancelled Revoked
Expired Revoked

Auto-Revoke

Access is revoked through the same status hooks. A WooCommerce Membership moving out of active status, or a subscription moving to on-hold, cancelled, or expired, removes the member from any spaces gated exclusively to that level.

Note: Revocation is driven by membership and subscription status changes, not by order Refunded transitions. Refunding a one-off order does not by itself revoke access - the gating membership or subscription has to change status.

Combining with Other Rules

WooCommerce rules stack with MemberPress, PMPro, and trust-level rules in the same space. A member gains access if they satisfy any rule - regardless of which rule type it is.

Example: gate a "Premium VIP" space to either MemberPress VIP level OR a specific WooCommerce product purchase. Members who qualify through either path are both added automatically.

Troubleshooting

Rule Type dropdown does not show WooCommerce - Confirm Jetonomy Pro is active, WooCommerce is active, and WooCommerce Memberships or WooCommerce Subscriptions is active. The adapter stays inactive (and the rule type is hidden) until one of those two extensions is present.

No plans or products to select - The dropdown lists WooCommerce Memberships plans and Subscription (including variable-subscription) products only. If it is empty, create a Membership plan or a Subscription product first. Plain Simple and Variable products are not selectable.

Member not removed after a refund - Access is tied to the gating membership or subscription status, not the order. Verify the WooCommerce Membership moved out of active status, or the subscription moved to on-hold, cancelled, or expired. A refunded order alone does not revoke access.

What's Next?

Gate spaces by LearnDash course or group enrollment - so students get discussion access automatically when they enroll.

LearnDash Integration →

Connect LearnDash course and group enrollment to Jetonomy spaces - students get dedicated discussion areas automatically when they enroll, and lose access when they un-enroll.

PRO - This feature requires Jetonomy Pro.

Jetonomy Settings - Integrations tab showing the integration status table and the Auto-Create Spaces for Courses card

LearnDash is the lead LMS integration in this section. The other four LMS guides (Tutor, LifterLMS, Sensei, MasterStudy) work the same way and link back here for the screenshots and the shared Access Rules walkthrough.

What You Will Learn

  • How to gate a Jetonomy space by LearnDash course enrollment
  • How to gate a space by LearnDash group membership
  • How to auto-create spaces when new courses are published
  • How to sync existing students into a space
  • What happens when a student un-enrolls

How Detection Works

Jetonomy Pro detects LearnDash automatically when both plugins are active. A LearnDash Course option appears in the Access Rules rule type dropdown - no setup needed. Compatible with LearnDash 4.x and 5.x.

Gating a Space by Course Enrollment

The Access Rules course picker with a searchable dropdown autocompleting course names as you type

  1. Go to Jetonomy → Spaces → open the space → Access Rules tab.
  2. Select LearnDash Course from the rule type dropdown.
  3. Start typing the course name - a searchable dropdown shows all published LearnDash courses.
  4. Select the course, set Grants to Participate and Space Role to Member.
  5. Click Add Rule.

The rule appears in the table showing the course name and a Sync Members button. For what the Grants and Space Role fields mean, see Grants and Space Role.

Gating a Space by LearnDash Group

LearnDash groups also appear in the searchable dropdown. This is ideal for cohort-based learning - one group, one space.

  1. Select LearnDash Course from the rule type dropdown.
  2. Type the group name - groups show as "Group Name (LD Group)" in the results.
  3. Select the group, set Grants and Space Role, and click Add Rule.

All members of the LearnDash group - including group leaders - gain access. When a user is removed from the group, they lose space access.

Syncing Existing Students

If students are already enrolled before the rule was created, click the Sync Members button. This pulls in all currently enrolled users. A notification shows how many were synced.

New enrollments and removals are handled automatically after the rule is created.

Auto-Create Spaces for New Courses

The Auto-Create Spaces for Courses card and the integration status table both live on the Integrations tab shown in the screenshot at the top of this guide (Jetonomy → Settings → Integrations).

  1. Go to Jetonomy → Settings → Integrations.
  2. Enable the LearnDash toggle under Auto-Create Spaces for Courses.
  3. Choose the default space type (Q&A, Forum, or Feed).
  4. Click Save Settings.

When you publish a new course in LearnDash, a private discussion space is automatically created with:

  • The course title as the space name
  • A membership access rule linking the course to the space
  • The course author assigned as space admin

Enrollment and Un-enrollment Events

LearnDash Event Jetonomy Action
Student enrolls in course Added to linked spaces as Member
Student completes course Access retained
Student un-enrolls or is removed Removed from linked spaces
User added to group Added to linked spaces as Member
User removed from group Removed from linked spaces

Content (posts and replies) remains in the space - only access is revoked.

Typical Setup for a Course Community

  • One Private space per course, gated to that course's enrollment
  • One Public space for general Q&A open to all students
  • One Hidden space per group/cohort, gated to a LearnDash Group

Troubleshooting

LearnDash Course does not appear in the rule type dropdown - Confirm Jetonomy Pro and LearnDash are both active. Check Jetonomy → Settings → Integrations to see the LearnDash status.

Students still have access after un-enrolling - Confirm the un-enrollment uses LearnDash's standard course access management. Custom enrollment plugins that bypass the learndash_update_course_access hook will not trigger removal.

Sync Members shows 0 synced - The students may already be space members, or no users are enrolled in the selected course.

What's Next?

Learn how to gate spaces using Restrict Content Pro subscriptions.

Restrict Content Pro Integration →

Gate Jetonomy spaces by Restrict Content Pro subscription level - with automatic access on activation and automatic removal on expiry or cancellation.

The Jetonomy admin settings screen

PRO - This feature requires Jetonomy Pro.

What You Will Learn

  • How Jetonomy Pro detects Restrict Content Pro (RCP)
  • How to create an Access Rule tied to an RCP subscription level
  • What membership events trigger auto-join and auto-leave
  • How to handle free and paid RCP levels differently

How Detection Works

Jetonomy Pro detects Restrict Content Pro automatically when both plugins are active. The RCP adapter registers with the Adapter Registry and adds Restrict Content Pro Level as a Rule Type in the Access Rules tab for every space.

Note: Jetonomy Pro supports Restrict Content Pro version 3.x and above. If you are on an older version, update RCP first.

Setting Up an Access Rule

  1. Go to Jetonomy → Spaces and open the space you want to gate.
  2. Click the Access Rules tab.
  3. Set Rule Type to Restrict Content Pro Level.
  4. Pick the subscription level in the Value field.
  5. Set Grants to Participate and Space Role to Member for a standard gated space.
  6. Click Add Rule.

Members with an active subscription to the selected level gain access immediately. You can add multiple rules to grant access across more than one RCP level. For what the Grants and Space Role fields mean, see Grants and Space Role.

Tip: RCP supports free membership levels. You can use a free level as a gate to require a (free) registration before members can post - while still keeping the community open to anyone willing to sign up.

Auto-Join on Activation

When a member's RCP subscription status becomes active, Jetonomy Pro adds them to any spaces that grant that subscription level. The standard Jetonomy hook fires:

add_action( 'jetonomy_membership_activated', function( int $user_id, string $level_id, string $adapter ) {
    // $adapter is 'rcp' for Restrict Content Pro events.
}, 10, 3 );

Auto-Leave on Expiry or Cancellation

When an RCP subscription expires, is cancelled, or is set to pending, Jetonomy Pro removes the member from any spaces gated exclusively to that level. The hook jetonomy_membership_deactivated fires with $adapter = 'rcp'.

RCP Status Access
Active Granted
Pending Revoked
Expired Revoked
Cancelled Revoked
Disabled Revoked

Combining with Other Adapters

RCP rules stack with all other Access Rule types in Jetonomy. A member gains access to a space if they satisfy any single rule - whether it comes from RCP, MemberPress, WooCommerce, or trust level.

Troubleshooting

RCP Level not appearing in Rule Type dropdown - Confirm Jetonomy Pro is active and Restrict Content Pro is active. Check that at least one subscription level exists in Restrict Content → Subscription Levels.

Member not removed after subscription expires - RCP can be configured to change status on expiry, grace periods, or manual review. Jetonomy listens to RCP 3.x membership API actions: rcp_membership_post_activate, rcp_membership_post_cancel, and rcp_transition_membership_status (which removes access on expired, cancelled, and pending). If a custom RCP workflow bypasses these actions, auto-removal will not fire.

Access still active after cancellation - Check whether the user holds a second active RCP subscription that also grants access to the space via a separate rule.

What's Next?

Learn how Jetonomy integrates with BuddyNext for a unified community hub experience.

BuddyNext Integration →

When BuddyNext is active alongside Jetonomy, the two plugins integrate automatically - sharing design tokens and letting BuddyNext own the page header and navigation, without any configuration.

The Jetonomy admin settings screen

What You Will Learn

  • How BuddyNext and Jetonomy split the work between a social layer and a forum layer
  • Why pairing the two in-house plugins beats bolting on a third-party social plugin
  • What the BuddyNext integration enables
  • How shared design tokens work
  • How Jetonomy suppresses its own header and nav when BuddyNext is active
  • What you need to do (spoiler: nothing)

The Social Layer for Jetonomy

BuddyNext is Wbcom's in-house social-networking layer, and Jetonomy is the forum, community, and Q&A layer. They are built to pair:

Layer Plugin Owns
Social BuddyNext Member profiles, the activity feed/stream, connections and the social graph
Forum Jetonomy Spaces, topics, ideas, and roadmaps

Run them together and you get a complete community platform from one vendor - social profiles and a member feed on the BuddyNext side, structured discussion and Q&A on the Jetonomy side. There is no third-party social plugin (BuddyPress or BuddyBoss) required: the social graph and the forum come from the same house and are designed to fit.

Already on BuddyPress? Jetonomy also pairs with BuddyPress directly - see the BuddyPress integration. BuddyNext is the path for site owners who want the social layer and the forum layer to come from a single vendor.

Why BuddyNext Over a Third-Party Social Plugin

For a site owner choosing what to run underneath Jetonomy, BuddyNext has practical advantages over a third-party social plugin:

  • One vendor. Social layer and forum layer ship from the same team, so there is one place to go for billing, licensing, and questions - not two ecosystems to reconcile.
  • Aligned roadmap and support. Features on both sides are planned together, so an integration point doesn't break when one plugin updates ahead of the other. Support sees the whole stack.
  • Native colour and dark-mode sync. Jetonomy adopts BuddyNext's design tokens automatically, so the forum matches the social layer in light and dark mode with no custom CSS. See Popular Page-Builder Themes and colour adoption for how the underlying token chain works.
  • Lighter footprint. No extra abstraction layer or compatibility shim between a third-party social plugin and Jetonomy - the two plugins talk through shared tokens and a single detection hook.

Auto-Detection

Jetonomy detects BuddyNext automatically - there are no settings to configure and no toggles to enable. When BuddyNext is active, Jetonomy adopts its colors and typography and lets BuddyNext own the page header and navigation.

Note: The integration activates only when BuddyNext 1.0 or higher is active and the BuddyNext community hub feature is enabled.

Shared Design Tokens

BuddyNext's TokenService injects CSS custom properties (--brand, --bg, --text-1, --green, --amber, --red, --r-md, --font-body, --font-display) into the page. Jetonomy's CSS inherits these automatically through its --jt-* token cascade:

--jt-accent: var(--brand, var(--wp--preset--color--primary, #3B82F6));
--jt-bg:     var(--bg,    var(--wp--preset--color--base,    #ffffff));
--jt-font:   var(--font-body, var(--wp--preset--font-family--body, inherit));

This means Jetonomy matches the BuddyNext visual style without you writing a single line of CSS. Accent color, typography, border radius, and dark mode all flow through automatically.

Header and Navigation Suppression

When BuddyNext is active, Jetonomy detects it via the buddynext_loaded action and suppresses its own community header and top navigation. BuddyNext owns the page chrome, so Jetonomy renders its content inside BuddyNext's layout without duplicating the header bar or navigation.

This keeps the page from showing two competing headers. When BuddyNext is not active, Jetonomy renders its own header and navigation as normal.

Dark Mode

BuddyNext's dark mode toggle sets data-theme="dark" on the document root. Jetonomy's dark mode overrides live in .jt-dark .jt-app and respond to this same attribute - so toggling dark mode in BuddyNext also applies to Jetonomy community pages instantly.

Developer Notes

Detection is driven by the buddynext_loaded action. Jetonomy's templates check did_action( 'buddynext_loaded' ) and skip rendering their own header and navigation when BuddyNext is present (see templates/partials/header.php and templates/partials/sidebar.php). There is no separate bridge class to configure - the suppression is built into the templates and runs automatically.

Troubleshooting

Jetonomy still showing its own header alongside BuddyNext - The buddynext_loaded action must fire before Jetonomy's templates render. Confirm BuddyNext is active and loading on the page, and that no caching layer is serving a pre-BuddyNext version of the page.

Design tokens not matching - BuddyNext's TokenService may not be firing on community pages. Check the BuddyNext community page template and ensure buddynext_loaded action fires before the wp_head hook.

What's Next?

Learn how Jetonomy adapts to any WordPress theme via CSS custom properties and template overrides.

Theme Compatibility →

Jetonomy works with any WordPress theme. Its CSS inherits from your theme's design tokens automatically, so the community looks native - not bolted on.

Community home page adapting to the active WordPress theme

What You Will Learn

  • How Jetonomy adapts its visual style to your active theme
  • What zero-config means with BuddyX
  • How to override community templates from your theme
  • How to control design tokens for fine-grained customization

How Theme Adaptation Works

Jetonomy reads WordPress theme.json values through CSS custom properties. Every --jt-* token in Jetonomy's CSS resolves first to a WordPress preset token (--wp--preset--color--primary, --wp--preset--font-family--body, etc.), with a hardcoded fallback last:

--jt-accent: var(--brand, var(--wp--preset--color--primary, #3B82F6));
--jt-text:   var(--text-1, var(--wp--preset--color--contrast, #1a1a1a));
--jt-font:   var(--font-body, var(--wp--preset--font-family--body, inherit));
--jt-radius: var(--r-md, var(--wp--custom--border-radius, 8px));

If your theme defines these standard WP preset tokens, Jetonomy adopts the theme's colors, typography, and spacing without you writing any CSS.

Best With BuddyX

BuddyX is Jetonomy's reference theme. With BuddyX active, Jetonomy requires zero configuration - colors, fonts, border radius, hover states, and dark mode all match the theme perfectly out of the box.

Tip: If you are building a new community site from scratch, start with BuddyX. You can always switch themes later - Jetonomy will adapt.

BuddyX Pro, Reign, and the Theme Bridge (1.3.0+)

Starting in 1.3.0, Jetonomy ships a dedicated bridge for the three Kirki-based themes most of our customers run: BuddyX, BuddyX Pro, and Reign.

What the bridge does

  • Reads the theme's Kirki mods on every render (accent color, dark mode state, container width).
  • Injects --jt-accent directly so the community picks up the exact color the customer chose in the Customizer - not a hardcoded fallback.
  • Toggles .jt-dark on the page <body> via body_class whenever the theme is in dark mode, so the community's dark overrides activate automatically without requiring custom CSS.
  • No configuration screen. If the theme is active, the bridge runs. If you switch to a non-Kirki theme, the bridge silently bows out and the standard theme.json path takes over.

Where it lives

includes/integrations/class-theme-integration.php - guarded by class_exists( 'Kirki' ) and a per-theme check against the theme template slug.

Why this matters

On BuddyX/BuddyX Pro/Reign, flipping the theme's dark-mode toggle in the Customizer now flips the entire community sidebar, nav, post cards, and reply editor in the same render. No custom-CSS bridge required.

If you build a custom Kirki theme and want to hook into the same bridge, the integration is extensible via the jetonomy_theme_light_tokens and jetonomy_theme_dark_tokens filters. Each receives a map of --jt-* token => hex color (light mode and dark mode respectively) - return your own token maps and Jetonomy will inject them. Return an empty array to disable that mode's override.

Starting in 1.5.0, Jetonomy extends automatic brand-color adoption to four widely-used page-builder themes. When any of these themes is active, the forum picks up its accent in both light and dark mode with no custom CSS required.

Theme Brand token adopted
Astra --ast-global-color-0
Kadence --global-palette1
GeneratePress --wp--preset--color--accent
Blocksy --theme-palette-color-1

Light and dark mode. The adoption works through a CSS var() chain: --jt-accent resolves to the active theme's brand token first. When the theme flips its own token for dark mode, the forum follows automatically - no separate dark-mode override needed.

Our own themes are also covered. BuddyX, BuddyX Pro, and Reign have been supported since 1.3.0 via the Kirki bridge described above. They define their own token names (--bx-color-accent for BuddyX and BuddyX Pro, --reign-colors-theme for Reign) and work the same way with no configuration required.

Themes with no recognizable brand token. Stock block themes that expose numbered palette slugs (such as accent-1 or palette3) are deliberately not auto-adopted - an accidentally adopted low-contrast color is worse than a clean default. For these themes, Jetonomy falls back to its own neutral accent. The site owner can override it under Jetonomy → Settings → Appearance → Color Palette.

For the var() token chain, the accent/token map, and how to extend theming for a new theme, see the Theming and Tokens developer guide.

Using Other Themes

Jetonomy works with any well-built WordPress theme. Compatibility level depends on how fully the theme uses theme.json:

Theme Type Expected Result
Modern block theme (theme.json) Excellent - tokens inherit fully
Classic theme with CSS variables Good - accent and font tokens pick up if variable names match
Classic theme without CSS variables Functional - Jetonomy falls back to its own neutral defaults

For classic themes, you can override the --jt-accent token in your theme's style.css or via Jetonomy → Settings → Appearance → Custom CSS.

Dark Mode

Jetonomy supports dark mode natively. If your theme sets data-theme="dark" or a .dark class on the <html> or <body> element, Jetonomy's dark overrides activate automatically via the .jt-dark .jt-app CSS selector.

BuddyX and BuddyNext set data-theme="dark" - so dark mode follows automatically. For other themes, add a small bridge if their dark mode uses a different selector:

/* In your theme's style.css - bridge for custom dark mode selector */
.my-theme-dark .jt-app { --jt-bg: #121212; --jt-text: #f0f0f0; }

Template Overrides

Jetonomy's frontend is powered by PHP templates. Every template can be overridden in your theme without touching plugin files.

Create a jetonomy/ folder inside your theme and copy any template file from wp-content/plugins/jetonomy/templates/ into it. Jetonomy's template loader checks the theme directory first.

Override directory structure:

your-theme/
└── jetonomy/
    ├── views/
    │   ├── community-home.php
    │   ├── space.php
    │   ├── single-post.php
    │   └── user-profile.php
    └── partials/
        ├── header.php
        └── reply-card.php

Note: When you override a template, you own it. Future Jetonomy updates will not touch your override. Check the plugin's template changelog after each update to merge any changes manually.

Customizing Design Tokens Without Template Overrides

For small visual adjustments, use the Custom CSS field in Jetonomy → Settings → Appearance. You can re-define any --jt-* token at the .jt-app scope:

.jt-app {
    --jt-accent: #e11d48;
    --jt-radius: 4px;
}

This approach is update-safe and does not require template overrides.

Available --jt-* Tokens

Category Tokens
Typography --jt-font, --jt-font-heading, --jt-font-mono
Accent --jt-accent, --jt-accent-hover, --jt-accent-light, --jt-accent-muted
Text --jt-text, --jt-text-secondary, --jt-text-tertiary
Background --jt-bg, --jt-bg-subtle, --jt-bg-muted, --jt-bg-hover
Border --jt-border, --jt-border-strong
Semantic --jt-success, --jt-warn, --jt-danger and their -light variants
Radius --jt-radius, --jt-radius-sm, --jt-radius-lg, --jt-radius-full

What's Next?

Configure your community's global settings - URL slug, pagination, and access defaults.

General Settings →

Connect Tutor LMS course enrollment to Jetonomy spaces - students get a dedicated discussion area automatically when they enroll, and lose access when they cancel or are removed.

PRO - This feature requires Jetonomy Pro.

Jetonomy Settings - Integrations tab showing the integration status table and the Auto-Create Spaces for Courses card

Tutor LMS works exactly like the other LMS integrations. The course picker, the Sync Members button, and the Auto-Create card are shown in the LearnDash guide, which is the lead LMS reference for this section.

What You Will Learn

  • How to gate a Jetonomy space by Tutor LMS course enrollment
  • How to auto-create spaces when new courses are published
  • How to sync existing students into a space
  • What happens when a student un-enrolls or cancels

How Detection Works

Jetonomy Pro detects Tutor LMS automatically when both plugins are active. A Tutor Course option appears in the Access Rules rule type dropdown - no setup needed.

Linking a Course to a Space

  1. Go to Jetonomy → Spaces and open (or create) the discussion space for your course.
  2. Open the Access Rules tab.
  3. Select Tutor Course from the rule type dropdown.
  4. Start typing your course name - a searchable dropdown appears showing all published Tutor courses (see the course picker screenshot).
  5. Select the course, set Grants to Participate and Space Role to Member.
  6. Click Add Rule.

The rule appears in the table showing the course name (not an ID), with a Sync Members button and Delete button. For what the Grants and Space Role fields mean, see Grants and Space Role.

Syncing Existing Students

If students are already enrolled in the course before the rule was created, click the Sync Members button next to the rule. This pulls in all currently enrolled students and adds them to the space. A toast notification shows how many were synced.

New enrollments and cancellations are handled automatically after the rule is created - no further action needed.

Auto-Create Spaces for New Courses

Instead of manually creating a space for each course:

  1. Go to Jetonomy → Settings → Integrations.
  2. Under Auto-Create Spaces for Courses, enable the Tutor LMS toggle.
  3. Choose the default space type (Q&A, Forum, or Feed).
  4. Click Save Settings.

Now when you publish a new course in Tutor, a private discussion space is automatically created with:

  • The course title as the space name
  • A membership access rule linking the course to the space
  • The course author assigned as space admin

Enrollment and Un-enrollment Events

Tutor LMS Event Jetonomy Action
Student enrolls in course Added to linked space as Member
Student completes course Access retained
Student enrollment cancelled Removed from linked space
Student enrollment deleted Removed from linked space

Content (posts and replies) created by the student remains in the space - only access is revoked.

Typical Setup

  • One Private space per paid course, gated to enrollment
  • One Public space per free course for open discussion
  • One Public space for general Q&A open to all students

Troubleshooting

Tutor Course does not appear in the rule type dropdown - Confirm Jetonomy Pro and Tutor LMS are both active. Check Jetonomy → Settings → Integrations to see the Tutor LMS status.

Students still have access after cancellation - Confirm the cancellation uses Tutor's standard enrollment management. Set the space to Private to fully restrict access.

Sync Members shows 0 synced - The students may already be space members, or no users are enrolled in the selected course.

What's Next?

LifterLMS Integration →

Connect LifterLMS course and membership enrollment to Jetonomy spaces - students get a dedicated discussion area when they enroll, and lose access when removed.

PRO - This feature requires Jetonomy Pro.

Jetonomy Settings - Integrations tab showing the integration status table and the Auto-Create Spaces for Courses card

LifterLMS works exactly like the other LMS integrations. The course picker, the Sync Members button, and the Auto-Create card are shown in the LearnDash guide, which is the lead LMS reference for this section.

What You Will Learn

  • How to gate a space by LifterLMS course or membership enrollment
  • How to auto-create spaces when new courses are published
  • How to sync existing students into a space

How Detection Works

Jetonomy Pro detects LifterLMS automatically when both plugins are active. A LifterLMS Course option appears in the Access Rules rule type dropdown.

Linking a Course to a Space

  1. Go to Jetonomy → Spaces → open the space → Access Rules tab.
  2. Select LifterLMS Course from the rule type dropdown.
  3. Start typing the course name - a searchable dropdown shows all published courses and memberships (see the course picker screenshot).
  4. Select the course, set Grants to Participate and Space Role to Member.
  5. Click Add Rule.

LifterLMS memberships also appear in the search - select a membership to gate a space by membership level instead of individual course enrollment. For what the Grants and Space Role fields mean, see Grants and Space Role.

Syncing Existing Students

Click the Sync Members button next to any rule to pull in all currently enrolled students. New enrollments and removals sync automatically.

Auto-Create Spaces for New Courses

  1. Go to Jetonomy → Settings → Integrations.
  2. Enable the LifterLMS toggle under Auto-Create Spaces for Courses.
  3. Choose the default space type and save.

When you publish a new LifterLMS course, a private space is created with the course name, an access rule, and the course author as space admin.

Enrollment and Un-enrollment Events

LifterLMS Event Jetonomy Action
Student enrolls in course Added to linked space as Member
Student added to membership Added to linked space as Member
Student completes course Access retained
Student removed from course Removed from linked space
Student removed from membership Removed from linked space
Enrollment permanently deleted Removed from linked space

Content remains in the space - only access is revoked.

Memberships and Courses

LifterLMS memberships can auto-enroll students in multiple courses. When a student joins a membership:

  • They gain access to spaces linked to the membership itself
  • They also gain access to spaces linked to the auto-enrolled courses (via the course enrollment hooks)

Troubleshooting

LifterLMS Course does not appear in dropdown - Confirm Jetonomy Pro and LifterLMS are both active. Check Jetonomy → Settings → Integrations.

Membership students not getting access - Ensure the access rule uses the membership level ID (shows as "Membership Name (LifterLMS Membership)" in the search). Course-level rules only apply to direct course enrollment.

What's Next?

Sensei LMS Integration →

Connect Sensei LMS course enrollment to Jetonomy spaces - students get a dedicated discussion area when enrolled, and lose access when withdrawn.

PRO - This feature requires Jetonomy Pro.

Jetonomy Settings - Integrations tab showing the integration status table and the Auto-Create Spaces for Courses card

Sensei LMS works exactly like the other LMS integrations. The course picker, the Sync Members button, and the Auto-Create card are shown in the LearnDash guide, which is the lead LMS reference for this section.

What You Will Learn

  • How to gate a space by Sensei LMS course enrollment
  • How to auto-create spaces when new courses are published
  • How to sync existing students into a space

How Detection Works

Jetonomy Pro detects Sensei LMS automatically when both plugins are active. A Sensei Course option appears in the Access Rules rule type dropdown.

Linking a Course to a Space

  1. Go to Jetonomy → Spaces → open the space → Access Rules tab.
  2. Select Sensei Course from the rule type dropdown.
  3. Start typing the course name - a searchable dropdown shows all published Sensei courses (see the course picker screenshot).
  4. Select the course, set Grants to Participate and Space Role to Member.
  5. Click Add Rule.

For what the Grants and Space Role fields mean, see Grants and Space Role.

Syncing Existing Students

Click the Sync Members button next to any rule to pull in all currently enrolled learners. New enrollments and withdrawals sync automatically.

Auto-Create Spaces for New Courses

  1. Go to Jetonomy → Settings → Integrations.
  2. Enable the Sensei LMS toggle under Auto-Create Spaces for Courses.
  3. Choose the default space type and save.

When you publish a new Sensei course, a private space is created with the course name, an access rule, and the course author (teacher) as space admin.

Enrollment and Un-enrollment Events

Sensei uses a single enrollment status change event that handles both enrollment and withdrawal:

Sensei LMS Event Jetonomy Action
Learner enrolled in course Added to linked space as Member
Learner completes course Access retained
Learner withdrawn from course Removed from linked space
Learner manually enrolled by admin Added to linked space as Member
Learner manually withdrawn by admin Removed from linked space

Content remains in the space - only access is revoked.

WooCommerce Integration

Sensei LMS integrates with WooCommerce for paid courses. When a student purchases a course through WooCommerce and Sensei enrolls them, the Jetonomy space access rule triggers automatically - no additional WooCommerce adapter needed for course gating.

Troubleshooting

Sensei Course does not appear in dropdown - Confirm Jetonomy Pro and Sensei LMS are both active. Check Jetonomy → Settings → Integrations.

Students not losing access after withdrawal - Ensure the withdrawal uses Sensei's standard enrollment management. Set the space to Private to fully restrict access.

What's Next?

MasterStudy LMS Integration →

Connect MasterStudy LMS course enrollment to Jetonomy spaces - students get a dedicated discussion area when they enroll in a course.

PRO - This feature requires Jetonomy Pro.

Jetonomy Settings - Integrations tab showing the integration status table and the Auto-Create Spaces for Courses card

MasterStudy LMS works like the other LMS integrations for adding members. The course picker, the Sync Members button, and the Auto-Create card are shown in the LearnDash guide, which is the lead LMS reference for this section. (One difference: MasterStudy does not fire a removal hook - see Important Note on Removal below.)

What You Will Learn

  • How to gate a space by MasterStudy LMS course enrollment
  • How to auto-create spaces when new courses are published
  • How to sync existing students into a space

How Detection Works

Jetonomy Pro detects MasterStudy LMS automatically when both plugins are active. A MasterStudy Course option appears in the Access Rules rule type dropdown.

Linking a Course to a Space

  1. Go to Jetonomy → Spaces → open the space → Access Rules tab.
  2. Select MasterStudy Course from the rule type dropdown.
  3. Start typing the course name - a searchable dropdown shows all published MasterStudy courses (see the course picker screenshot).
  4. Select the course, set Grants to Participate and Space Role to Member.
  5. Click Add Rule.

For what the Grants and Space Role fields mean, see Grants and Space Role.

Syncing Existing Students

Click the Sync Members button next to any rule to pull in all currently enrolled students. New enrollments sync automatically.

Auto-Create Spaces for New Courses

  1. Go to Jetonomy → Settings → Integrations.
  2. Enable the MasterStudy LMS toggle under Auto-Create Spaces for Courses.
  3. Choose the default space type and save.

When you publish a new MasterStudy course, a private space is created with the course name, an access rule, and the course author as space admin.

Enrollment Events

MasterStudy LMS Event Jetonomy Action
Student enrolls in course Added to linked space as Member
Student completes course Access retained

Important Note on Removal

MasterStudy LMS does not fire a hook when students are removed from courses. This means automatic removal from linked spaces is not available. To manage access:

  • Use the Sync Members button periodically to re-sync - students no longer enrolled will not be re-added
  • Remove students manually from the space Members tab
  • For subscription-based access, consider pairing MasterStudy with WooCommerce Memberships which does fire removal hooks

Troubleshooting

MasterStudy Course does not appear in dropdown - Confirm Jetonomy Pro and MasterStudy LMS are both active. Check Jetonomy → Settings → Integrations.

Students remain in space after course access revoked - MasterStudy does not fire removal hooks. Remove students manually from the space Members tab or use the Sync Members button to verify current enrollment.

What's Next?

BuddyNext Integration →

When FluentCommunity is active alongside Jetonomy, the two plugins coexist as one product. Members navigate between the social feed and the forum without noticing two separate systems, and admins pair spaces in one click.

What You Will Learn

  • How the integration auto-detects FluentCommunity
  • How to pair FluentCommunity spaces with Jetonomy spaces
  • How member sync, activity broadcast, and the comment-to-reply bridge work
  • How the cross-profile links and unified avatars behave
  • What stays separate (private content, moderation, notifications)

Auto-Detection

Jetonomy detects FluentCommunity automatically - there is nothing to install or configure to turn the integration on. As soon as both plugins are active, the connecting surfaces (the Discussions tab, the cross-profile links, member sync, and activity broadcast) become available.

Jetonomy never writes directly to FluentCommunity's database; it only adds members and feed posts through FluentCommunity's own functions. You can deactivate FluentCommunity at any time and both plugins keep working independently, with no leftover data.

Developer note: detection runs on every page load via class_exists( 'FluentCommunity\App\App' ). All writes go through FluentCommunity's public helpers (addToSpace) and the Feed model - never raw SQL.

Pairing Spaces

Open Jetonomy Settings > FluentCommunity. You will see:

  • A status line confirming FluentCommunity is detected
  • A tab-label field (default "Discussions")
  • A pairings table where each row maps one FluentCommunity space to one Jetonomy space

Click Add pair, pick the FluentCommunity space on the left and the Jetonomy space on the right, and save. The pairing is stored in a single WordPress option (jetonomy_fc_space_pairs). Unpair a row by setting the Jetonomy column to "Not paired".

Once a pair is saved, you get five connected surfaces automatically:

  1. A Discussions tab appears on the FluentCommunity space header, linking to the paired Jetonomy space.
  2. An Also on {your community} card appears in the Jetonomy space sidebar, linking back to the FluentCommunity feed.
  3. The FluentCommunity profile gets a Discussions block showing the member's five most recent topics started plus the five topics they follow on Jetonomy.
  4. The Jetonomy profile gets a View on {your community} cross-link to the member's FluentCommunity profile.
  5. Member avatars unify across both sides: the FluentCommunity avatar is used everywhere on the site, including Jetonomy pages.

The tab, card, and button labels pick up the Site Title configured in FluentCommunity so the wording matches your community brand. The Discussions label is configurable from the settings page.

Member Sync

When a member joins a paired FluentCommunity space, they are automatically added to the paired Jetonomy space. The reverse also holds: joining the Jetonomy space enrols the member in the FluentCommunity space.

Sync is add-only by design:

  • Joins propagate in both directions.
  • Leaves do not propagate. Removing yourself from one side never yanks you out of the other.
  • Role changes do not propagate. Each plugin manages its own role structure.

Member sync is enabled by default and can be toggled off from the settings page. There is also a Sync existing members now button that performs a one-click backfill: every member already in one side of a pair is enrolled into the other side. Backfill is safe to re-run and capped at 5,000 members per space per run.

Activity Broadcast

When a new topic is created in a paired Jetonomy space, an announcement feed post appears in the paired FluentCommunity space with the topic title, excerpt, and a discreet "Shared from the forum" attribution link.

Properties:

  • One-way only. Broadcast runs from Jetonomy to FluentCommunity. FluentCommunity feed posts never silently create forum topics.
  • Private topics are never broadcast. If a topic is marked private in Jetonomy, nothing is posted to FluentCommunity. The FC feed audience can be broader than the private-topic scope, so private content stays where it was authored.
  • Paragraph breaks preserved. The excerpt keeps its formatting through the HTML-to-plain conversion.
  • No duplicated titles. The feed post uses the topic title as its heading once, with the excerpt as body and a footer link back to the Jetonomy topic.

Broadcast is enabled by default and can be toggled off from the settings page.

Comment-to-Reply Bridge

When a member comments on one of the broadcast feed posts in FluentCommunity, the comment is mirrored back as a reply on the original Jetonomy topic, preserving author attribution.

Only comments on broadcast feed posts round-trip. Native FluentCommunity feed posts (ones Jetonomy did not broadcast) are left alone. Edits and deletes on FluentCommunity do not propagate: the forum thread remains the durable record of record.

Cross-Profile Navigation

Navigation between the two profile systems works in both directions:

  • FluentCommunity profile: the Discussions block ends with a View all on forum link pointing to the Jetonomy profile.
  • Jetonomy profile: a View on {your community} button points to the FluentCommunity profile.

The FluentCommunity site title is read from the fluent_community_settings option's site_title key, so the button text always matches what your FC admin has branded the community with. If you have not set one, the button falls back to your WordPress site name, then to "Community".

Identity Keying

Everything joins on user_id, never on username. This matters when a user has different usernames on the two sides (for example WP user admin with FluentCommunity xprofile username admin2). user_id is the primary key in both wp_fcom_xprofile and wp_jt_user_profiles, so the integration stays correct no matter how usernames diverge.

Stability Guarantees

  • No core changes to FluentCommunity or Jetonomy. Everything lives in one integration file in the Jetonomy plugin.
  • No writes to FluentCommunity tables outside FC's public helpers. Remove the integration and FluentCommunity is untouched.
  • Stale pair handling. If either side's space ID does not resolve (deleted space, renamed slug), the tab and card silently disappear. No admin cleanup needed.
  • Graceful degradation. If a FluentCommunity hook is ever renamed or removed in a future FC release, only the corresponding surface stops rendering. The rest of the integration keeps working.
  • One option row. The pairing map is stored in a single WordPress option. Uninstall deletes one row.

What Is Not Integrated (Yet)

Kept out of the first release on purpose:

  • Leave sync. Destructive semantics: pulling someone off one side when they leave the other is too likely to cause surprise removals. Add-only stays the default.
  • Privacy mirroring. Paired spaces do not share visibility settings. Exposing or hiding content across two plugins silently is a product decision, not a hook decision.
  • Profile field sync. Bios, display names, and custom fields are not synced two-way.
  • Notification inbox merge. Each plugin keeps its own notification stream.
  • Unified search. FC and Jetonomy have different search surfaces; querying both simultaneously is not wired.
  • Shared moderation queue. Different content models, different flag rules.

Open a request if one of these is the feature you need. We ship what customers actually use.


Building on top of the integration? See the FluentCommunity integration reference in the Developer Guide for options, hooks, and extension points.

When BuddyPress is active alongside Jetonomy, groups and forum spaces feel like one membership. Members who join a group are enrolled in the paired forum space, new topics are announced on the group activity stream, and comments on those activities flow back to the topic as replies.

What You Will Learn

  • How the integration auto-detects BuddyPress
  • How to pair a group with a forum space (create a new one or link an existing one)
  • How member sync and role sync work across groups and spaces
  • How topics broadcast to the group activity stream and how comments round-trip back as replies
  • What surfaces appear on group pages and member profiles

Auto-Detection

Jetonomy detects BuddyPress automatically when the Groups component is active - there is nothing to install or configure to turn the integration on. The pairing options, member sync, the Forum tab, and the profile nav all become available as soon as both plugins are active.

If you have BuddyPress Groups enabled but the Activity component turned off, everything still works - Jetonomy simply skips the activity-stream broadcast and the comment bridge, which need Activity.

Developer note: detection runs on every page load via bp_is_active( 'groups' ); the broadcast and comment-bridge pieces additionally check bp_is_active( 'activity' ) at runtime.

Pairing Groups and Spaces

Group-to-space pairs are set directly inside BuddyPress's group creation wizard and the group Manage > Details screen. The Jetonomy integration adds a Discussion Forum section with three choices:

  • No forum. The group has no paired forum space. Default for new groups.
  • Create new discussion forum. Creates a matching Jetonomy space with the group's name, description, and visibility (public → public, private → private, hidden → hidden). The group creator becomes the space admin.
  • Link existing forum. Pick from a dropdown of unlinked spaces the current user already owns or moderates. Site admins see every unlinked space.

The pairing is stored as jetonomy_space_id in BuddyPress's group meta (one value per group). Unpairing a group is as simple as setting the dropdown back to "No forum".

Member Sync

Once a group is paired, membership changes propagate both ways:

  • Join a BP group → added to the paired Jetonomy space as a member.
  • Join the paired Jetonomy space → added to the BP group as a member.
  • Leave or be removed from the BP group → removed from the paired space.
  • Banned from the group → removed from the space.
  • Unbanned → re-added to the space.
  • Promoted to group admin → space admin.
  • Promoted to group moderator → space moderator.
  • Demoted → back to space member.

Unlike the FluentCommunity integration, BuddyPress member sync is bidirectional for both adds and leaves. Groups and forum spaces are treated as a single membership, which is the model most BuddyPress sites expect.

Want add-only sync? If you would rather a member who leaves a group keep their forum-space access (the way the FluentCommunity integration behaves), a developer can disable leave-sync with a few lines of code. See Disable member-leave propagation in the BuddyPress integration reference.

Forum Tab on BP Group Pages

Paired groups gain a Forum tab in their group nav. Clicking it renders a list of the most recent topics in the paired space with reply counts, author, and time-ago, plus a + New Topic button for signed-in members.

The listing is visibility-aware: private topics only appear to the author or a moderator, so activity-stream widgets and group-page readers never see content they shouldn't.

Forum Nav on BP Member Profiles

Every member profile gets a Forum primary nav item with three sub-tabs:

  • Posts: the member's most recent topics with reply counts and links.
  • Replies: the member's most recent replies with links to the original topics.
  • Bookmarks: topics the member has bookmarked. Own profile only; others don't see your bookmarks.

A stats block above each sub-tab shows the member's posts, replies, votes, reputation, and trust level at a glance. A View Full Forum Profile link takes them to the Jetonomy profile for deeper history.

Back-to-Group Banner on Jetonomy Pages

When a member clicks through from a BP group to a paired Jetonomy space (or a topic in it), a subtle back-link banner at the top of the Jetonomy page lets them return to the group with one click. Keeps the navigation continuous.

On paired Jetonomy spaces, the sidebar About card shows a small tag linking to the paired BP group. Useful for members who enter the space from the forum side and want to jump back to the group's activity feed or members list.

Activity Broadcast (New)

When a new topic is created in a paired Jetonomy space, an activity item is posted to the paired BP group's activity stream with:

  • An action line: "Someone started a new forum topic in Group Name" (rendered by BuddyPress with the standard member + group links).
  • The topic excerpt as paragraphs.
  • A discreet "Shared from the forum · View discussion" attribution line at the bottom.

Properties:

  • One-way only. Broadcast runs from Jetonomy to BuddyPress. BP activity posts never silently create forum topics.
  • Private topics are never broadcast. If a topic is marked private on the Jetonomy side, no activity item is created. The group audience can be broader than the private-topic scope on the forum side.
  • Private/hidden groups stay private. The activity is posted with hide_sitewide set, so site-wide activity feeds do not leak the item outside the group.
  • Paragraph breaks preserved. The excerpt keeps its structure because the integration whitelists <br> and <p> on bp_activity_allowed_tags for broadcast content (tags are harmless on their own, with no attributes allowed through).

Broadcast is enabled by default. To turn it off, go to Jetonomy → Settings → Integrations and clear the Broadcast topics to group activity checkbox. The Integrations tab appears only while BuddyPress Groups is active. These controls were added to the admin UI in 1.5.0; prior versions required wp option update jetonomy_bp_broadcast 0 via WP-CLI.

Comment-to-Reply Bridge (New)

When a member comments on one of those broadcast activity items, the comment is mirrored back to the originating Jetonomy topic as a reply, with author attribution preserved.

Only comments on broadcast activities round-trip. Comments on native BP activity posts (status updates, other plugins' activity types) are left alone. The integration identifies broadcast activities by a jetonomy_post_id activity-meta marker it sets at post time.

Add-only by design: edits and deletes on the BuddyPress side do not propagate back. The forum thread stays the durable record. Enabled by default. To turn it off, go to Jetonomy → Settings → Integrations and clear the Round-trip activity comments checkbox - it requires Broadcast topics to group activity to stay on. This control was added to the admin UI in 1.5.0; prior versions required wp option update jetonomy_bp_comment_bridge 0 via WP-CLI.

Identity Keying

Everything joins on user_id, which is the same primary key for BuddyPress user profiles and Jetonomy user profiles. Usernames and display names can diverge between the two without breaking the integration.

Stability Guarantees

  • No core changes to BuddyPress. Everything lives in Jetonomy's integration class; BP tables are read via its public API.
  • Group meta is the only data footprint on the BP side (one jetonomy_space_id key per paired group). Deactivating Jetonomy leaves BuddyPress untouched.
  • Graceful degradation. If BuddyPress is removed, the BP-specific surfaces disappear (the integration class is gated on bp_is_active('groups')), and the paired Jetonomy spaces continue to work on their own.
  • Activity component optional. The broadcast and comment bridge gate themselves on bp_is_active('activity') at runtime, so sites with BP Groups but not Activity still get member sync and forum tabs.
  • Stale pair handling. If a paired space is deleted on the Jetonomy side, the forum tab on the BP group quietly disappears on next render. No admin cleanup required, no fatal.

What Is Not Integrated (Yet)

  • Cross-plugin notification merge. BuddyPress notifications and Jetonomy notifications live in separate inboxes. Members see them in two places.
  • Unified search. Each plugin has its own search UX.
  • Shared moderation queue. Group moderation and forum moderation are independent. A user banned from a group is removed from the space but not flagged for forum moderation, and vice versa.
  • Two-way edit/delete sync on comments and replies. The comment bridge creates a reply on round-trip but does not keep the reply and the activity comment in sync after that point.

Building on top of the integration? See the BuddyPress integration reference in the Developer Guide for hooks, options, and extension points.

Connect Learnomy course enrollment and membership-plan subscriptions to Jetonomy spaces - students get a discussion area automatically when they enroll or subscribe, and lose access when they un-enroll or their subscription ends.

PRO - This feature requires Jetonomy Pro.

Jetonomy Access Rules tab showing a saved membership rule with its Type, Value, Grants, and Space Role columns

Learnomy stores its courses, membership plans, enrollments, and subscriptions in its own custom tables, not in WordPress posts. Jetonomy Pro connects to it through Learnomy's public model API, so gating a space by a Learnomy course works the same as the LearnDash flow.

What You Will Learn

  • How to gate a Jetonomy space by Learnomy course enrollment
  • How to gate a space by a Learnomy membership plan
  • How to sync existing students into a space
  • What happens when a student un-enrolls or a subscription ends

How Detection Works

Jetonomy Pro detects Learnomy automatically when both plugins are active. Detection requires the LEARNOMY_VERSION constant plus Learnomy's Enrollment and Subscription model classes, so the option only appears when Learnomy is present and exposing the API this integration needs. A Learnomy Course option then appears in the Access Rules rule type dropdown - no setup needed.

Gating a Space by Course Enrollment

The Access Rules course picker with a searchable dropdown autocompleting course names as you type

  1. Go to Jetonomy → Spaces → open the space → Access Rules tab.
  2. Select Learnomy Course from the rule type dropdown.
  3. Start typing the course name - a searchable dropdown filters the Learnomy catalog. Courses show as "Course Name (Learnomy Course)".
  4. Select the course, set Grants to Participate and Space Role to Member.
  5. Click Add Rule.

The rule appears in the table showing the course label and a Sync Members button. For what the Grants and Space Role fields mean, see Grants and Space Role.

The catalog picker lists up to 500 courses and plans. Filter jetonomy_learnomy_max_levels raises or lowers that cap when a site's catalog is larger.

Gating a Space by Membership Plan

Learnomy membership plans appear in the same Learnomy Course picker alongside courses. Plans show as "Plan Name (Learnomy Membership)" in the results.

  1. Select Learnomy Course from the rule type dropdown.
  2. Type the membership plan name and pick it from the results.
  3. Set Grants and Space Role, and click Add Rule.

Members with an active subscription to that plan gain access. When the subscription is cancelled or expires, access is removed.

Syncing Existing Students

If students are already enrolled or subscribed before the rule was created, click the Sync Members button on the rule. This checks every user against the linked course or plan and adds the ones who currently hold it. The button reports how many members were synced.

New enrollments, subscriptions, and removals are handled automatically after the rule is created.

Enrollment and Subscription Events

Learnomy Event Jetonomy Action
Student enrolls in course Added to linked spaces at the rule's Space Role
Student un-enrolls from course Removed from linked spaces
Course enrollment expires Removed from linked spaces
Membership plan subscription created Added to linked spaces at the rule's Space Role
Membership plan subscription cancelled Removed from linked spaces
Membership plan subscription expires Removed from linked spaces

On each add or remove, Jetonomy fires jetonomy_membership_activated or jetonomy_membership_deactivated with the source set to learnomy. Content the member created in the space stays in place - only access is revoked.

Troubleshooting

Learnomy Course does not appear in the rule type dropdown - Confirm Jetonomy Pro and Learnomy are both active, and that Learnomy has at least one published course.

Students still have access after un-enrolling - Confirm the un-enrollment uses Learnomy's standard enrollment API. Custom code that bypasses the learnomy_student_unenrolled or learnomy_enrollment_expired hooks will not trigger removal.

A course or plan is missing from the picker - The picker lists up to 500 entries. Raise the cap with the jetonomy_learnomy_max_levels filter, or confirm the plan is active in Learnomy - inactive plans are not returned.

What's Next?

Learn how to gate spaces by a WP Fusion CRM tag.

WP Fusion Integration →

Connect WP Fusion CRM tags to Jetonomy spaces - when a member gains or loses a tag in your CRM, Jetonomy Pro adds or removes them from every linked space automatically. Joining a space can also apply tags back to the CRM, so the link runs both ways.

PRO - This feature requires Jetonomy Pro.

Jetonomy spaces live in custom database tables, not WordPress posts, so WP Fusion's built-in content-restriction meta box does not reach them. This integration is the seam that connects a CRM tag to a space.

What You Will Learn

  • How Jetonomy Pro detects WP Fusion
  • How to gate a space by one or more CRM tags
  • How joining and leaving a space can write tags back to the CRM
  • What happens when a member's tags change in the CRM

How Detection Works

Jetonomy Pro detects WP Fusion when the wp_fusion() function is available and returns a usable user object. A dedicated WP Fusion tab then appears on every space under Jetonomy → Spaces → Edit Space. If WP Fusion is not active, the tab shows an inactive message. The tag picker stays empty until WP Fusion is connected to a CRM under WP Fusion → Settings.

Adding a WP Fusion Tag Rule to a Space

  1. Go to Jetonomy → Spaces and open the space you want to gate.
  2. Click the WP Fusion tab.
  3. In the Linked tags field, search for and select one or more CRM tags. The picker uses WP Fusion's own searchable control, which lazy-loads tags in accounts with more than 1,000 tags.
  4. Click Save WP Fusion Settings.

Any member who holds one of the linked tags is admitted to the space the next time Jetonomy evaluates their access. From then on, whenever a member's tags change in the CRM, Jetonomy Pro reconciles their space memberships automatically.

What the Three Fields Do

The WP Fusion tab has three settings per space.

Linked tags

These are the tags that grant access to this space. A member who holds any of the linked tags is added to the space. The linked tags are also written to the Access Rules tab as a membership rule, so both tabs share one source of truth.

Apply tags on join

These extra tags are granted to the member in the CRM when they join the space through any route - accepting an invite, an admin adding them, or gaining a linked tag. Use this to tag members who enter the space so downstream CRM automations can react. These tags do not grant space access on their own.

On leave - Remove linked tags

When enabled, a member who leaves the space has the linked tags removed in the CRM. Leave this off if the tag also gates other content outside Jetonomy that a departing member should keep.

Setting the Space Role

The WP Fusion tab creates the access rule at the Member role by default. To change the role - for example, to give certain tag holders Moderator access - open the Access Rules tab on the same space and adjust the role for the WP Fusion rule. Both tabs share the same underlying rule, so a change in one is reflected in the other. The Current access linkage summary at the bottom of the WP Fusion tab lists which tags currently grant access to the space.

How Reconciliation Works

WP Fusion fires the wpf_tags_modified action whenever a member's CRM tag set changes, passing the full new tag set. Jetonomy Pro responds by walking only the WP Fusion tags that are actually referenced by a space rule, then adding the member to spaces where they now hold the tag and removing them from spaces where they no longer do.

Reconciliation is triggered by CRM updates, not by polling. Access changes take effect as soon as WP Fusion processes the tag change from the CRM.

Troubleshooting

WP Fusion tab is not visible on the space - Confirm that Jetonomy Pro is active and licensed, and that WP Fusion is active.

Tag picker is empty - Confirm WP Fusion is connected to a CRM under WP Fusion → Settings. Tags are only available after a CRM connection is established.

Member still has access after their tag was removed - Confirm the tag removal actually triggered a sync in WP Fusion. Check the member's WP Fusion activity log. If WP Fusion did not process the change, Jetonomy Pro never receives the wpf_tags_modified event.

Member does not gain access after being tagged - Same check: confirm WP Fusion processed the tag addition and the member's WP Fusion log shows the new tag.

What's Next?

Learn how to gate spaces by a SureMembers access group.

SureMembers Integration →

Connect SureMembers access groups to Jetonomy spaces - when a member is granted an access group, Jetonomy Pro adds them to every linked space, and revoking the group removes them. Joining a space can also grant access groups back to the member, so the link runs both ways.

PRO - This feature requires Jetonomy Pro.

SureMembers restricts WordPress post-type content natively. Jetonomy spaces live in custom database tables, not posts, so SureMembers' standard content-restriction settings do not apply to them. This integration is the way to connect a SureMembers access group to a Jetonomy space.

What You Will Learn

  • How Jetonomy Pro detects SureMembers
  • How to gate a space by one or more access groups
  • How joining and leaving a space can grant or revoke access groups
  • What happens when an access group is granted or revoked

How Detection Works

Jetonomy Pro detects SureMembers when its Access_Groups and Access classes are available. A dedicated SureMembers tab then appears on every space under Jetonomy → Spaces → Edit Space. If SureMembers is not active, the tab shows an inactive message. Only active SureMembers access groups appear in the picker.

Adding a SureMembers Rule to a Space

  1. Go to Jetonomy → Spaces and open the space you want to gate.
  2. Click the SureMembers tab.
  3. In the Linked access groups field, select one or more access groups from the list.
  4. Click Save SureMembers Settings.

Members who hold one of the linked access groups are admitted to the space the next time their access is evaluated. From then on, when SureMembers grants or revokes an access group, Jetonomy Pro updates the member's space membership automatically.

What the Three Fields Do

The SureMembers tab has three settings per space.

Linked access groups

These are the access groups that grant membership to this space. A member who holds any of the linked access groups is added to the space. Access is checked through SureMembers' own logic, so an expired or inactive access group is treated as not held and access is removed.

Apply access groups on join

These extra access groups are granted to the member through SureMembers when they join the space through any route. Use this to open protected SureMembers content to members at the moment they enter the space. These access groups do not grant space access on their own.

On leave - Remove linked access groups

When enabled, a member who leaves the space has the linked access groups revoked in SureMembers. Leave this off if the access group also gates other SureMembers-protected content the member should keep.

Setting the Space Role

The SureMembers tab creates the access rule at the Member role by default. To change the role, open the Access Rules tab on the same space and adjust the role for the SureMembers rule. Both tabs share the same underlying rule. The Current access linkage summary at the bottom of the SureMembers tab lists which access groups currently grant access to the space.

How Reconciliation Works

SureMembers fires suremembers_user_access_group_granted when a member receives an access group and suremembers_user_access_group_revoked when one is taken away. Jetonomy Pro listens to both actions and adds or removes the member from every space that references the affected access group.

Because access is evaluated through SureMembers' own access check, an access group that has expired is treated as not held. The member is removed from the space when SureMembers fires the revoked action for them.

Troubleshooting

SureMembers tab is not visible on the space - Confirm that Jetonomy Pro is active and licensed, and that SureMembers is active.

Access group list is empty - Confirm you have at least one active access group in SureMembers → Access Groups. Inactive or draft access groups do not appear in the picker.

Member still has access after their access group was revoked - Confirm the revocation went through SureMembers' standard API so the suremembers_user_access_group_revoked action fires. Code that updates the database directly without firing the action will not trigger removal.

Member does not gain access after being granted an access group - Confirm the grant used SureMembers' standard API so the suremembers_user_access_group_granted action fires. Check whether the space lists the access group under Current access linkage at the bottom of the SureMembers tab.

What's Next?

Return to the Integrations overview to see every membership, LMS, and community integration in one place.

Admin Settings

Configure every aspect of your Jetonomy community.

The General settings tab is the first place to go after installation. It controls your community URL, pagination defaults, and who can read or participate.

What You Will Learn

  • How to change your community's base URL slug
  • What the default space type setting controls
  • How to set your community's display title
  • How to configure pagination for posts and replies
  • How the Public / Private access control works

General settings

Go to Jetonomy → Settings to access these options. All changes take effect on save.

Community Base URL

Setting: base_slug Default: community Location: General tab → Community URL section

This is the URL prefix for all Jetonomy pages on your site. With the default value, your community home is at yoursite.com/community/. Spaces live at yoursite.com/community/s/space-name/, and so on.

You can change this to any URL-safe string, for example forum, hub, or discuss. Jetonomy automatically flushes rewrite rules when you change the base URL and save settings.

Warning: Changing the base URL after your community has content will break all existing links. If you must change it on a live site, set up 301 redirects from the old slug to the new one.

Community Title

Setting: community_title Default: Community Location: General tab → Community URL section

The display name of your community. It is shown as the main heading at the top of the community home page. Change it to your brand or community name (for example "Acme Support Forum" or "The Builders Hub"). This is a display label only - it does not change the base URL or any links.

Default Space Type

Setting: default_space_type Default: forum Options: Forum, Q&A, Ideas, Feed

When you create a new space in the admin, this setting pre-fills the Type dropdown. It is a convenience setting only - you can always change the type on any individual space. It does not affect existing spaces.

Choose the type that best matches the primary purpose of your community:

  • Forum - open discussion, replies sorted by date
  • Q&A - questions and answers, accepted answers float to top
  • Ideas - feature requests and votes, status workflow
  • Feed - short-form cards, optional title, chronological feed

Posts Per Page

Setting: posts_per_page Default: 20 Location: General tab → Pagination section

Controls how many posts appear per page in space listings and search results. A lower number is faster on large communities; a higher number reduces clicks for users who prefer scrolling. This value also controls how many additional posts load each time a member clicks Load More on a space listing.

Tip: For communities with 10,000+ posts per space, keep this at 20 or lower. Higher values increase page load time and database query time proportionally.

Replies Per Page

Setting: replies_per_page Default: 30 Location: General tab → Pagination section

Controls how many replies load per page inside a single post view. This value also controls how many additional replies load each time a member clicks Load More in a thread. Pagination starts at the oldest replies and works forward. Members can jump to the last page to see the most recent replies.

Guest Access (Public / Private)

Setting: guest_read Default: Public (guest reading on) Location: General tab → Access Control card

This is the single control that decides whether logged-out visitors can read your community. On the Access Control card it appears as two radio buttons - Public community and Private community - not a checkbox.

  • Public community (default) - logged-out visitors can read all public spaces, posts, and member profiles. They see a prompt to log in when they try to vote, reply, or follow a space.
  • Private community - every community page redirects guests to sign-in, and the REST API rejects unauthenticated requests. Choose this for members-only communities you do not want indexed by search engines.

Note: Even in Public mode, anonymous posting is not supported. "Public" means read-only browsing for logged-out visitors. Visitors must always log in to post, reply, or vote.

This Public/Private toggle is the same guest_read setting documented in full - including exactly which pages stay reachable in Private mode and how the REST API behaves - on the Access Control page.

Community as Homepage

Setting: front_page Default: off Location: General tab → Community Setup section

When enabled, visitors to your site root see the community home page instead of the WordPress front page. This takes precedence over the WordPress Settings → Reading → Your homepage displays setting. All community URLs, posts, pages, and feeds keep working unchanged.

Front-end Space Creation

Setting: frontend_space_creation_roles Default: empty (admin-only) Location: General tab → Community Setup section

Controls which WordPress roles may create spaces from the front end at /community/new-space/. Tick any roles you trust to create spaces. Site administrators always qualify regardless of the selection. Leave every box unticked to keep front-end space creation admin-only.

Require Email Verification

Setting: require_email_verification Default: off Location: General tab → Access section

When this is on, new members who register through Jetonomy's Login block receive a confirmation email immediately after sign-up. They cannot log in until they click the verification link in that email. Existing members are not affected - this setting applies only to accounts created after you enable it.

A follow-up reminder is sent automatically if the member has not confirmed within the configured window.

Setting: verification_reminder_hours Default: 24 Location: Stored in jetonomy_settings['verification_reminder_hours']

This controls how many hours after registration the reminder email is sent. The reminder runs on an hourly WP-Cron schedule (hook: jetonomy_verification_reminder). The email template can be customized on the Settings → Email screen under the "Verification reminder" row.

Note: If you use a third-party registration flow (WooCommerce, Restrict Content Pro, LearnDash) instead of Jetonomy's Login block, those plugins handle their own email verification. This setting only applies to registrations that go through the Jetonomy Login block.

Rebuild Counters

Location: Dashboard → Quick Actions, or via WP-CLI / REST API

Jetonomy stores denormalized counters (reply counts per post, post counts per space, member counts per space, vote scores) for performance. These counters are updated in real time on every write, but they can drift from true values after bulk database edits, server failures, or plugin imports.

Use Rebuild Counters to recalculate all denormalized values from the canonical tables.

WP-CLI:

wp --path="/path/to/wordpress" jetonomy recount
wp --path="/path/to/wordpress" jetonomy recount --type=posts   # posts only
wp --path="/path/to/wordpress" jetonomy recount --type=spaces  # spaces only
wp --path="/path/to/wordpress" jetonomy recount --type=votes   # votes only
wp --path="/path/to/wordpress" jetonomy recount --type=users   # user profile counters only

REST API (site admin only):

POST /jetonomy/v1/admin/recount
{ "type": "all" }

Valid type values: all, posts, spaces, votes, users. Omit or pass all to rebuild everything. The response reports how many rows were updated per step. On a large community (100,000+ posts), a full recount may take 10-60 seconds - run it during low-traffic hours.

Tip: After running an import from bbPress, wpForo, or Asgaros, always run a full recount. The importers insert rows directly and skip the counter-update logic.

Admin Bar Shortcut

All logged-in members see a Community menu in the WordPress admin bar. This menu links to the community home, your notification inbox, and your profile.

Members who have the jetonomy_manage_spaces or manage_options capability also see an admin sub-section with direct links to: Manage Spaces, Add New Space, Categories, Moderation Queue, Posts and Replies, and Settings.

The admin bar menu appears on both the public site and inside wp-admin, so you can jump from any page in the dashboard directly into the community and back.

Settings Save Confirmations

After you click Save Changes, a confirmation banner appears at the top of the settings page. The banner stays visible until you dismiss it - it does not disappear automatically. This ensures you always have a clear signal that your changes were saved.

What's Next?

Configure trust level thresholds and rate limits to control who can do what in your community.

Permission Settings →

  • White Label - replace Jetonomy branding with your own.
  • Analytics - community health and engagement dashboard.
  • Webhooks - send community events to external services.

The Permissions tab controls how quickly members earn trust in your community and how much they can do before you have had a chance to evaluate their behavior.

Permissions settings panel with trust level thresholds and rate limits

What You Will Learn

  • What Jetonomy's trust level system is and how it auto-promotes members
  • How to configure the thresholds for each trust level
  • What each trust level unlocks
  • How to adjust rate limits to match your community's size and risk tolerance

Go to Jetonomy → Settings → Permissions to access these settings.

The Trust Level System

Jetonomy uses six trust levels (0 through 5) to gradually extend posting privileges to members as they demonstrate good behavior. Levels 0 through 3 are earned automatically by the background cron job. Levels 4 and 5 are granted manually by moderators or admins.

Level Name Earned By
0 Newcomer Default on registration
1 Member Automatic - light activity
2 Regular Automatic - consistent participation
3 Trusted Automatic - high engagement and reputation
4 Leader Manual - granted by moderator or admin
5 Moderator Manual - granted by admin only

The cron job runs every 12 hours and re-evaluates every member against the configured thresholds. Demotion is also possible - if a member is muted or receives too many spam flags, their trust level can drop.

Trust Level Thresholds

Setting: trust_thresholds Location: Permissions tab → Trust Levels section

Each auto-promotion level (1, 2, and 3) has a configurable set of thresholds. A member must meet all thresholds for a level to be promoted to it.

Threshold Description Configurable
posts Minimum posts created Yes
days_active Minimum days since registration Yes
reputation Minimum reputation score Yes
replies_received Minimum replies received on own posts Yes

Default thresholds (Level 1):

Threshold Default Value
posts 5
days_active 3
reputation 0
replies_received 10

For small communities (under 200 members), lower the thresholds. Members can feel stuck if Level 1 requirements take weeks to meet. For larger communities, raise the thresholds to protect against spam waves.

Tip: Start with low thresholds and tighten them if you see abuse. It is easier to tighten later than to manually promote members who are stuck at Level 0.

What Each Trust Level Unlocks

Ability Level Required
Read public spaces 0 (any visitor)
Create posts 0
Reply to posts 0
Add images to posts 1
Include external links 1
Use @mentions 1
Follow spaces 0
Vote on posts and replies 0
Flag content for moderation 1
Edit own posts 0
Delete own posts 1
Access invite links 1
Create invite links 2
Skip anti-spam checks 2
Moderate content (space moderator) Assigned by admin

Note: The anti-spam exemption at Level 2 is important. Members who have earned Level 2 are trusted enough to skip reCAPTCHA and Turnstile checks entirely. This keeps the experience smooth for your most active members.

Rate Limits

Setting: rate_limits Location: Permissions tab → Rate Limits section

Rate limits cap how many actions a member can take in a given time window. They protect your community from spam bursts and accidental double-submissions.

Action Default Limit Window
Create post 3 per day
Create reply 10 per day
Vote 5 per day

Trust Level 1+ members are exempt from all rate limits.

For high-traffic communities, raise these limits. For communities experiencing spam problems, lower them. Changes take effect immediately - no cache flush needed.

Adjusting for Community Size

Small community (under 500 members):

  • Lower all trust thresholds significantly - active members should reach Level 2 within a week
  • Keep rate limits at defaults - spam volume is low
  • Consider setting days_active to 0 for Level 1 to avoid frustrating early adopters

Large community (10,000+ members):

  • Keep thresholds at defaults or raise them - organic promotion still happens, just slower
  • Raise the reputation and posts thresholds to make higher levels harder to reach
  • Lower post rate limits if you see spam bursts

Reputation Points

Setting: jetonomy_settings['reputation_points'] Location: Permissions tab → Reputation Points section

Every community action awards or deducts a fixed number of reputation points. You can override any default value on this screen. Positive numbers reward the action; negative numbers penalize it.

Action key Label Default
post_upvoted Post upvoted +10
reply_upvoted Reply upvoted +5
post_downvoted Post downvoted -2
reply_downvoted Reply downvoted -2
reply_accepted Reply accepted as answer +15
idea_planned Idea moved to Planned/Shipped +20
flag_validated Flag confirmed (reporter) +5
post_reported Post reported -10
post_removed Post removed by moderator -20

The Default column shows each value before any override is saved. You can restore a row to its default by deleting your override and saving again.

Lookup order at runtime: saved admin override in jetonomy_settings['reputation_points'] → hardcoded defaults above → 0. This means a site with no overrides still scores correctly out of the box.

Developer hook: jetonomy_reputation_points_for

add_filter( 'jetonomy_reputation_points_for', function( int $points, string $action ): int {
    // Double the reward for replies accepted as answers.
    if ( 'reply_accepted' === $action ) {
        return $points * 2;
    }
    return $points;
}, 10, 2 );

The filter runs after the admin override and hardcoded fallback resolve, so $points already reflects any per-site override. Return an int.

What's Next?

Configure which notification types are enabled by default and set your community email identity.

Email Settings →

The Email settings tab controls which notification emails Jetonomy sends, what name and address they come from, and how to test your email configuration.

Email settings with From name, From address, and notification type toggles

What You Will Learn

  • Which notification types can be toggled on or off
  • How to set your community's From name and From address
  • How to send a test email to confirm delivery
  • How to connect an SMTP plugin for reliable email delivery

Go to Jetonomy → Settings → Email to access these settings.

How Jetonomy Sends Email

Jetonomy uses WordPress's built-in wp_mail() function for all outgoing notifications. This means it is immediately compatible with any SMTP plugin you already use - WP Mail SMTP, FluentSMTP, Postman, or any other. No extra configuration in Jetonomy is needed; just configure your SMTP plugin and Jetonomy benefits automatically.

Tip: On production sites, always use an SMTP plugin with a transactional email service (Mailgun, Postmark, SendGrid, SES). The default PHP mail() delivery is unreliable and frequently lands in spam.

From Name

Setting: email_from_name Default: Your WordPress site name Location: Email tab → Sender section

This is the display name that appears in the From field of every Jetonomy notification email. Use your community or product name - something members will recognize immediately in their inbox.

From Address

Setting: email_from_email Default: WordPress admin email Location: Email tab → Sender section

This is the email address that appears in the From field. Use a dedicated address such as community@yoursite.com or noreply@yoursite.com.

Warning: The From address must be a verified sender with your email service provider. Using an unverified address causes high bounce rates and spam scoring. If you use Gmail SMTP, the From address must match your Google account.

Branding the Email

Two optional fields let you brand every notification email with your own logo and footer line. Both live on the Email tab and are empty by default - leave them blank to send plain, text-only emails.

Setting: email_logo_url Default: Empty (your site name is shown as text instead) Location: Email tab → Sender section

Paste the full URL to a logo image (for example https://yoursite.com/logo.png). The image appears at the top of every notification email. A landscape logo around 200x40px in PNG or SVG works best. If you leave this empty, Jetonomy shows your community name as plain text at the top instead.

Tip: Use an absolute https:// URL hosted on your own site or media library. Relative paths and http:// URLs are blocked by many email clients.

Setting: email_footer_text Default: Empty (placeholder text: "You received this because you are a member of the community.") Location: Email tab → Footer Text section

A short line shown at the very bottom of every branded notification email - typically a reminder of why the member is receiving it. If you leave it blank, the placeholder line above is used.

Notification Toggles

Setting: notification_defaults Location: Email tab → Notification Types section

Each notification type has an independent toggle for both web (in-app bell) and email delivery. The defaults shown here are the site-wide defaults. Individual members can override their own preferences from their notification settings page.

Notification Type Web Default Email Default
Reply to your post On On
Reply to a reply you made On Off
@mention On On
Accepted answer (Q&A) On On
Your idea roadmap status changed On On
New post in followed space On Off
Badge earned On Off
Vote on your post On Off
Moderator action on your content On On
Space join request On On

The values you set here are the starting defaults for new members. Individual members can still override any type from their own notification settings. Use these defaults to keep noisy notification types quiet out of the box without locking members out of re-enabling them.

Note: Vote and badge notifications default to web-only because they can occur frequently. Email for every vote would quickly train members to ignore your community emails entirely.

Test Email

Location: Email tab → bottom of page → Send Test Email button

Click Send Test Email to send a test message to the WordPress admin email address. The test email confirms that wp_mail() is working and that your From name and address are applying correctly.

If the test email does not arrive within a few minutes, check:

  1. Your SMTP plugin's log for send errors
  2. Your spam folder
  3. That the From address is verified with your email provider

Email and Jetonomy Pro

Jetonomy Pro adds two additional email capabilities:

  • Email Digest - daily and weekly summary emails that bundle multiple notifications into one. Members set their preference per notification type.
  • ESP Adapters - native integrations for SendGrid, Mailgun, Amazon SES, and Postmark that bypass wp_mail() for higher throughput and detailed delivery analytics.

Both are managed via Jetonomy → Extensions after installing Jetonomy Pro.

What's Next?

Control the visual appearance of your community - accent color, font inheritance, layout density, and custom CSS.

Appearance Settings →

The Appearance settings tab gives you direct control over the visual style of your community - from a single accent color override to a full custom CSS field.

Appearance settings with accent color picker, font options, and layout density controls

What You Will Learn

  • How Jetonomy adopts your theme's brand color automatically
  • How to set a custom accent color to override it
  • How layout density affects the community UI
  • How to use the custom CSS field safely

Go to Jetonomy → Settings → Appearance to access these settings.

How Jetonomy's Visual System Works

Jetonomy uses CSS custom properties (--jt-* tokens) throughout its stylesheet. Every color, font, radius, and spacing value references a token. By default, those tokens inherit from your active theme's theme.json values automatically.

The Appearance tab gives you a set of override controls on top of that inheritance layer. You can use them without writing any CSS.

Brand Color

Setting: accent_color Default: #0073aa shown in the picker, which means "not set - adopt the active theme's brand color" Location: Appearance tab → Colors section

The accent color drives buttons, links, vote arrows, trust-level highlights, and other interactive elements.

By default Jetonomy adopts the active theme's own brand color automatically, with no custom CSS from you. It reads each theme's native brand token, falling back to the next one in this chain until it finds one:

  1. BuddyX and BuddyX Pro
  2. Reign
  3. BuddyNext
  4. Any theme's WordPress primary color preset
  5. Astra
  6. Kadence
  7. Blocksy
  8. Neve
  9. GeneratePress (and any theme's WordPress accent color preset)
  10. A neutral default (#0073aa) when the theme exposes none of the above

Because Jetonomy reads each theme's live token rather than a fixed value, the adopted color follows the theme in both light and dark mode. When the theme repaints its brand token for dark mode, Jetonomy's accent repaints with it.

Jetonomy does not inject its own color into your site's global or block-editor color palette. It reads the theme's color rather than overriding it, and the plugin's own theme.json is merged in only as a baseline layer, so the active theme's presets always win.

To pin an exact accent instead of the adopted one, set this picker to any value other than the #0073aa default. Your chosen color then overrides the theme adoption on every theme, and theme updates no longer change Jetonomy's accent. Leaving the picker at #0073aa keeps automatic adoption active.

Tip: You do not need to worry about button-text legibility. Jetonomy runs a built-in WCAG contrast guard: it measures the accent color it ends up using and automatically flips button text to black or white so it stays AA-readable, even on pale or muted theme brand colors. The theme's brand color itself is never changed - only the text on top of it adapts.

Color Palette

Location: Appearance tab → Color Palette section

Beyond the accent color, you can set the community's core surface colors directly. This is useful when your theme has no color tokens for Jetonomy to inherit, or when you simply want exact surface colors. Any field you set overrides the corresponding default; leave a field empty to keep the default - secondary shades (hover, muted text) derive automatically.

Each field accepts a hex color value:

  • Text (text_color) - Body copy and headings. Secondary and muted text derive from it.
  • Background (bg_color) - Cards and content surfaces.
  • Subtle Background (bg_subtle_color) - Secondary surfaces such as table headers, code, and quiet panels.
  • Border (border_color) - Card outlines, dividers, and input borders.

Layout

Jetonomy 1.4.0 added a Layout panel with three controls that decide how the community canvas sits inside your active theme. Every option defaults to Theme Default, so existing installs see no visual change after the upgrade. When you do change a value, Jetonomy emits a small block of CSS scoped to body.jt-page - the rules only apply on community routes and never leak into the rest of your site.

Container Width

Setting: container_width Default: Theme Default Options: Theme Default, Full Width, Custom (px) Location: Appearance tab → Layout section

Controls how wide the community canvas can grow before it stops expanding.

  • Theme Default - Inherits the host theme's content container width. Use this when your theme's reading width already feels right.
  • Full Width - Lets the community stretch edge-to-edge of the viewport. Best for kanban-style spaces, leaderboards, and dense feeds that benefit from horizontal room.
  • Custom (px) - Pins the canvas to a specific pixel width (e.g. 1280). Useful when you want a wider reading column than the theme provides without going fully edge-to-edge.

Theme Sidebar

Setting: sidebar_visibility Default: Theme Default Options: Theme Default, Hide on community pages Location: Appearance tab → Layout section

Decides whether the host theme's sidebar shows on community routes.

  • Theme Default - Leaves the theme's sidebar exactly where the theme renders it.
  • Hide on community pages - Suppresses the host theme's sidebar across /community/* so the forum renders at full width even when the rest of the site has a sidebar everywhere. Pair this with Full Width above when you want a true full-bleed community experience.

Page Padding

Setting: padding_preset Default: Theme Default Options: Theme Default, None, Comfortable Location: Appearance tab → Layout section

Adjusts the inline padding around the community canvas.

  • Theme Default - Uses whatever inline padding the theme provides.
  • None - Removes the inline padding so the community sits flush against the viewport edges. Good for themes that already hug the edges.
  • Comfortable - Adds a generous inline padding. Useful for themes that hug the edges too tightly and leave content butting against the screen on mobile.

Tip: If your theme has a sidebar everywhere but you want the community to feel like a standalone app, set Container Width to Full Width, Theme Sidebar to Hide on community pages, and Page Padding to Comfortable. The rest of your site keeps the original theme layout.

Layout Density

Setting: layout_density Default: comfortable Options: Compact, Comfortable, Spacious Location: Appearance tab → Layout section

Compact - Reduced padding and tighter spacing. Fits more content on screen at once. Best for high-volume spaces where members scan many posts quickly.

Comfortable - Standard spacing between post cards, reply cards, and interface elements. Best for general communities and long-form discussion.

Spacious - Extra-roomy spacing between cards and interface elements. Best for low-volume, reading-focused communities that favor breathing room over density.

When you change this setting, Jetonomy adds data-jt-density="compact" (or "comfortable") to the .jt-app wrapper element. CSS rules keyed to this attribute apply the appropriate spacing.

Custom CSS

Setting: custom_css Default: Empty Location: Appearance tab → Custom CSS section

The Custom CSS field accepts any valid CSS. Jetonomy outputs this CSS as an inline style block at the end of the <head> on all community pages, scoped after the main jetonomy.css stylesheet.

Use this field to override --jt-* tokens, adjust component styles, or add community-specific visual tweaks:

/* Override accent color and border radius */
.jt-app {
    --jt-accent: #7c3aed;
    --jt-radius: 12px;
}

/* Increase heading size in post titles */
.jt-post-title {
    font-size: 1.25rem;
}

Note: Custom CSS is output as-is - no minification, no scoping, no sandboxing. Write only CSS you control. If you enter invalid CSS here, it may break parts of the community UI.

Tip: For larger CSS customizations, consider using a child theme's style.css or a dedicated CSS plugin instead of this field. The Custom CSS field is best for quick, targeted overrides.

What's Next?

Configure how Jetonomy appears in search engines - XML sitemaps, schema markup, and meta title patterns.

SEO Settings →

The SEO settings tab controls how Jetonomy pages appear in search engines - XML sitemaps, structured data, meta title patterns, and robots directives for specific page types.

SEO settings with sitemap toggle, meta title patterns, and robots directives

What You Will Learn

  • How Jetonomy generates SEO-friendly URLs for community pages
  • How to enable or disable the XML sitemap
  • How schema markup works for forum content
  • How to configure meta title patterns
  • How to exclude certain page types from search engine indexing

Go to Jetonomy → Settings → SEO to access these settings.

How Jetonomy Generates URLs

Every Jetonomy page has a clean, human-readable URL structured around your community base slug:

Page Type URL Pattern
Community home /community/
Category /community/category/slug/
Space /community/s/slug/
Post /community/s/space-slug/t/post-slug/
User profile /community/u/username/
Tag /community/tag/slug/
Search /community/search/

Post slugs are auto-generated from the post title, truncated at 60 characters, and deduplicated with a numeric suffix if needed. All URLs use standard WordPress rewrite rules and are compatible with all SEO plugins.

XML Sitemap

Setting: seo_sitemap Default: On Location: SEO tab → Sitemap section

When enabled, Jetonomy registers a custom sitemap provider with WordPress's built-in sitemap API. Community pages appear at /wp-sitemap.xml alongside your regular WordPress content.

The sitemap includes:

  • All public spaces
  • All public posts (paginated if over 2,000)
  • User profile pages

Private, hidden, and archived spaces are excluded. Draft and scheduled posts are excluded.

Tip: Check Settings → Reading → Search engine visibility is not set to "Discourage search engines" or your sitemap will be disregarded.

Location: SEO tab → Sitemap section

Right next to the toggle is a button that surfaces the live sitemap URL for your install (typically https://example.com/wp-sitemap.xml). Copy the URL straight from the admin and submit it to:

You only need to submit the sitemap once per search engine. Google and Bing both recrawl the file automatically after the first submission, so new posts and spaces appear in the index without any further action.

Schema Markup

Setting: seo_schema Default: On Location: SEO tab → Structured Data section

When enabled, Jetonomy outputs JSON-LD structured data on community pages. This helps search engines understand your content and can improve how your pages appear in search results.

Page Type Schema Type
Community home WebSite + SearchAction (sitelinks search box)
Space CollectionPage + ItemList
Q&A topic with an accepted answer QAPage (Question + Answer)
Discussion topic DiscussionForumPosting with vote and reply counts
Tag CollectionPage + ItemList
User profile Person

Pages with a breadcrumb trail also carry BreadcrumbList. QAPage and DiscussionForumPosting are the schema types Google documents for forum content: a question with an accepted answer is marked up as QAPage, and a regular discussion is marked up as DiscussionForumPosting with its up-vote and reply counts. Both are eligible for rich results in search.

Meta Title Patterns

Settings: seo_post_title, seo_space_title Location: SEO tab → Title Patterns section

These patterns control the <title> tag on Jetonomy pages. Use the available tokens to build the pattern you want.

Available tokens:

Token Output
{post_title} The post title
{space_name} The space name
{site_name} Your WordPress site name

Default post title pattern: {post_title} - {space_name} | {site_name}

Default space title pattern: {space_name} | {site_name}

Tip: Keep titles under 60 characters to avoid truncation in search results. The {site_name} suffix is good for brand recognition but costs characters. For long space names, consider omitting {site_name}.

Noindex Controls

Settings: seo_noindex_profiles, seo_noindex_search Default: On for both profiles and search Location: SEO tab → Robots section

Noindex user profiles - When enabled, Jetonomy adds <meta name="robots" content="noindex"> to all /community/u/*/ pages. Enable this if you prefer profiles not to appear in search results (common for privacy-sensitive communities).

Noindex search results - When enabled, the /community/search/ page is excluded from indexing. This is on by default because search results pages provide minimal SEO value and can create duplicate-content signals.

Open Graph and Twitter Card Tags

Jetonomy outputs Open Graph and Twitter Card meta tags automatically on all public community pages. No setting is needed. These tags control how your posts appear when shared on social media:

  • og:title - the post or space title
  • og:description - the post excerpt (first 160 characters of body text)
  • og:type - article for posts, website for other pages
  • og:url - the canonical URL
  • twitter:card - summary (or summary_large_image if a post has an image attachment)

Note: If you use an SEO plugin like Yoast SEO, RankMath, or The SEO Framework, its OG tags may override Jetonomy's. This is fine - SEO plugin output takes priority via standard WordPress wp_head hook priority ordering.

Twitter / X Handle

Setting: seo_twitter_handle Default: Empty Location: SEO tab → Twitter handle

Enter your community's Twitter or X handle (with or without the leading @). When set, Jetonomy emits twitter:site and twitter:creator meta tags on every community page that does not already declare a per-page override. This gives X / Twitter a verified attribution to display alongside link previews and enables richer card layouts.

Leave this empty if the community has no Twitter / X presence - Jetonomy simply omits the tags instead of emitting empty ones.

Default Share Image

Setting: seo_default_og_image Default: Empty Location: SEO tab → Default share image

Paste the absolute URL of an image to use as the fallback og:image whenever a specific post does not have an image of its own. This is a plain URL field, not a media-library picker. Posts that already include their own image continue to use that image - this setting only kicks in when there is nothing else to show. When left empty, Jetonomy falls back to the WordPress site logo / icon.

Recommended specs:

Property Value
Dimensions 1200 x 630 px
Format PNG or JPG
File size Under 5 MB
Aspect ratio 1.91:1

Twitter / X, Facebook, LinkedIn, and Slack all read this image when generating link previews, so picking a branded fallback (logo + community name on a clean background) keeps shared links recognizable.

Social Embeds (Instagram & Facebook)

Settings: fb_app_id, fb_app_secret Location: SEO tab → Social Embeds section

When members paste a link, Jetonomy turns it into a rich embed. YouTube, Vimeo, TikTok, Twitter/X, Spotify, SoundCloud, and TED Talks embed automatically with no setup required.

Instagram and Facebook are the exception: Meta deprecated anonymous oEmbed access in October 2020, so those two providers require a free Meta Developer App.

  • Facebook App ID (fb_app_id) - the numeric App ID from your Meta Developer dashboard.
  • Facebook App Secret (fb_app_secret) - the App Secret. It is stored in wp_options and never exposed to the frontend.

Leave both fields empty if you do not need Instagram or Facebook embeds - every other provider keeps working without them.

What's Next?

Set up anti-spam protection to keep your community clean without frustrating legitimate members.

Anti-Spam Settings →

The Anti-Spam tab lets you add invisible bot protection to post and reply submission - without disrupting the experience for legitimate members.

Anti-spam settings with provider selection, API keys, and score threshold

What You Will Learn

  • Which anti-spam providers Jetonomy supports
  • How to add your API keys and enable protection
  • How the score threshold works for reCAPTCHA v3
  • Which members are automatically exempt from checks

Go to Jetonomy → Settings → Anti-Spam to access these settings.

How It Works

Anti-spam checks run server-side before a post or reply is saved. When a submission fails the check, Jetonomy blocks the save and returns an error to the user. The check is invisible to legitimate members - no checkbox to tick, no image to identify.

Members at Trust Level 2 or above are exempt from all anti-spam checks. Admins are always exempt. This ensures your most active, trusted members never encounter friction.

Choosing a Provider

Setting: captcha_provider Default: None (none) Options: None (none), Google reCAPTCHA v3 (recaptcha_v3), Cloudflare Turnstile (turnstile)

Provider How It Works User Visibility
None No spam protection -
Google reCAPTCHA v3 Score-based, no user interaction Invisible (small badge)
Cloudflare Turnstile Smart challenge, no image puzzle Invisible (small badge)

Google reCAPTCHA v3 assigns a risk score (0.0 to 1.0) to each submission. You set a threshold - submissions below it are blocked.

Cloudflare Turnstile is GDPR-friendlier than reCAPTCHA and does not show a challenge unless it detects suspicious behavior. It is a good default for EU communities.

Tip: If you are seeing bot spam despite having anti-spam enabled, also check your Trust Level 0 rate limits in the Permissions tab. Combining low rate limits with Turnstile stops most automated spam without any user friction.

Google reCAPTCHA v3

  1. Go to google.com/recaptcha/admin and create a new site.
  2. Select reCAPTCHA v3 as the type.
  3. Add your domain to the allowed domains list.
  4. Copy the Site Key and Secret Key.
  5. Paste them into the corresponding fields in Jetonomy → Settings → Anti-Spam.
  6. Set the Score Threshold (see below).
  7. Save settings.

Site Key and Secret Key

Both supported providers (reCAPTCHA v3 and Turnstile) use the same two fields on this tab to store the credentials you copy from the provider's dashboard:

Setting: captcha_site_key Default: Empty Location: Anti-Spam tab → Site Key field

The public key that the protection widget loads in the browser. Safe to expose - it is meant to be visible in page source.

Setting: captcha_secret_key Default: Empty Location: Anti-Spam tab → Secret Key field

The private key Jetonomy uses server-side to verify each submission with the provider. Keep it confidential - it is rendered as a masked password field and is never exposed to the browser.

Configuration Warnings

Jetonomy checks your provider and key combination as the settings page loads and shows an inline "Heads up" notice at the top of the CAPTCHA Provider card whenever the two do not match. There are two cases:

  • Provider selected but keys missing - you picked reCAPTCHA v3 or Turnstile but left the Site Key and/or Secret Key blank. The notice reads: "A CAPTCHA provider is selected but the Site Key and/or Secret Key is empty. The CAPTCHA will not render until both keys are filled in." Resolve it by pasting both keys from the provider dashboard, then saving.
  • Keys saved but provider disabled - you saved a Site Key and/or Secret Key but left the Provider set to Disabled. The notice reads: "CAPTCHA keys are saved but the Provider is set to Disabled, so no CAPTCHA renders. Select Cloudflare Turnstile or reCAPTCHA above to activate it." Resolve it by choosing a provider from the dropdown, then saving.

The warning is advisory - it does not block saving. It clears on the next page load once the provider and keys agree.

Score Threshold (reCAPTCHA v3 Only)

Setting: captcha_score_threshold Default: 0.5 Range: 0.1 to 0.9

Google's reCAPTCHA v3 returns a score between 0.0 (likely bot) and 1.0 (likely human). Jetonomy blocks any submission with a score below your threshold.

Threshold Effect
0.3 Block only very confident bots. May let some spam through.
0.5 Balanced default. Blocks most bots, rarely affects humans.
0.7 Strict. May occasionally block mobile users or VPN users.

Start at 0.5. If you see false positives (legitimate members getting blocked), lower to 0.4. If spam is still getting through, raise to 0.6.

Cloudflare Turnstile

  1. Go to dash.cloudflare.comTurnstile and add a new site.
  2. Select Invisible as the widget mode.
  3. Add your domain to the allowed hostnames list.
  4. Copy the Site Key and Secret Key.
  5. Paste them into Jetonomy → Settings → Anti-Spam.
  6. Save settings.

There is no score threshold for Turnstile - Cloudflare handles the risk scoring internally. A submission either passes or fails.

Note: Turnstile requires your domain to be exactly correct in the Cloudflare dashboard. www.yoursite.com and yoursite.com are treated as different hostnames.

Trust Level Exemptions

Members at Trust Level 2 or above are never shown a challenge and their submissions never go through the anti-spam check. The check is skipped entirely on the server side.

This is intentional. Trust Level 2 requires consistent participation over time - members who earn it are clearly not bots.

WordPress admins (manage_options capability) are also always exempt, regardless of trust level.

Testing Your Setup

  1. Log out of WordPress completely.
  2. Go to any space on your community.
  3. Try to create a post or reply.
  4. If protection is working, the submission should complete (legitimate human request) and no error should appear.

To test blocking, lower the reCAPTCHA threshold to 0.9 temporarily and try submitting. If the error message appears, your integration is working correctly. Reset to 0.5 afterward.

What's Next?

Migrate your existing community data from bbPress or wpForo into Jetonomy.

Importing from bbPress →

The Access Control setting decides whether your community is open to the public or hidden behind sign-in. Pick the right mode in one place and Jetonomy enforces it across every page and the REST API.

What You Will Learn

  • The difference between Public and Private community modes
  • Which pages stay reachable in Private mode (sign-in, register, lost password)
  • How REST API access changes between the two modes
  • When to switch and what to expect

Go to Jetonomy → Settings → General and find the Access Control card to choose the mode. It is one card on the General tab, not a separate sub-page - the same guest_read toggle introduced under Guest Access on the General Settings page.

Public Mode (default)

Anyone - including search engines and visitors who haven't signed in - can read posts, replies, and member profiles. Posting and voting still require sign-in.

This is the default for every community and is unchanged from prior versions. Existing communities continue working without any setting change after upgrading to 1.4.1.

Use Public mode when:

  • You want search engine traffic to find your community
  • You're running a customer support forum or open knowledge base
  • New visitors should be able to browse before signing up

Private Mode

Every community page requires sign-in. Guests visiting /community/ or any space, post, tag, or profile URL are redirected to the sign-in page. The REST API also rejects unauthenticated requests for community data.

The sign-in, register, and forgot-password pages stay reachable so guests can still create an account or recover access.

Use Private mode when:

  • The community is for paying members only
  • Discussions are confidential (internal team, private group, paid coaching)
  • You don't want search engines to index any community content

What Stays Public in Private Mode

These pages are intentionally exempt so guests can sign up and recover access:

  • The sign-in page
  • The registration page
  • The forgot-password page
  • Email verification and password-reset confirmation links

Everything else - homepage, spaces, posts, replies, tags, member profiles, leaderboard, search - is gated.

REST API Behaviour

Public mode: read endpoints return data to anyone. Write endpoints still require auth.

Private mode: every endpoint under /wp-json/jetonomy/v1/ requires an authenticated request. Anonymous calls return 401 Unauthorized. This is checked centrally - third-party clients calling the API see the same gate as the website does.

Switching Modes

Switching is instant and applies to the next page load. You can change the mode at any time:

  • Public → Private: existing public links become sign-in redirects. Search engines will eventually drop your indexed pages.
  • Private → Public: pages become reachable again. Submit your sitemap to search engines if you want re-indexing.

There's no migration step and no downtime.

The Activity Log admin page shows you every audit-worthy event in your community - who created a post, who approved a reply, who banned a member, when a role changed. Read-only and filterable.

What You Will Learn

  • What events are recorded in the Activity Log
  • How to filter the log by user, event type, or date range
  • Why some events appear and others don't
  • How to use the log for moderation reviews and account audits

Go to Jetonomy → Activity Log to access the page.

Activity Log admin page showing a filtered list of community events with user, type, and date-range filters

What Gets Logged

The log records every event that changes the state of the community in a way that's worth being able to look back on:

  • Posts created, edited, deleted, restored
  • Replies created, edited, deleted, restored
  • Flags raised and resolved (with the resolution outcome)
  • Members joining or leaving a space
  • Role changes (member → moderator, moderator → admin, demotions)
  • Trust level promotions and demotions
  • Bans and silences (issued and lifted)
  • Setting changes that affect community behaviour

What's NOT logged: votes, reads, search queries, page views - these are too high-volume to keep useful and are tracked separately by Analytics (Pro).

Filters

The page supports three independent filters that can be combined:

Filter What it does
User Show only events caused by a specific member or staff user
Type Show only one event type (e.g. "post created", "ban issued")
Date range Show events between two dates

Combine all three to answer questions like "which posts did this moderator approve last week?"

Read-Only

The Activity Log is intentionally read-only. You can't edit or delete entries from this page. The log is the audit trail - modifying it would defeat the point.

If you need to undo something a member or staff user did, do it in the relevant area (e.g. restore a deleted post from the post page, lift a ban from the member's profile). The log will then record the corrective action as a new entry.

Performance

The log is paginated server-side at 20 entries per page by default (adjustable via the per-page screen option). Filters apply at the database level, so even on a community with hundreds of thousands of entries, filtered queries return in under a second on a normal host.

There's no automatic cleanup - entries are kept indefinitely. If you want to prune the log, you can do it via WP-CLI; reach out via support if you need a recipe.

The Revisions admin page lets you browse every saved revision of every post and reply, and compare any two of them side-by-side. Use it to see what changed when a member edits, or to recover content from an earlier version.

What You Will Learn

  • When Jetonomy saves a revision (and when it doesn't)
  • How to find a specific revision by post, user, or date
  • How to read the side-by-side diff
  • The relationship between the Revisions page and the post-level edit history

Go to Jetonomy → Revisions to access the page.

When a Revision Is Saved

A revision is saved every time a post or reply is edited and the change is meaningful - that is, the content actually differs from the previous version. Pure formatting tweaks (e.g. adding a paragraph break that doesn't change the rendered output) won't always create a revision.

Revisions are NOT created when:

  • The post is first published - that's the initial version, not a revision
  • The edit is from the same user within the auto-save window (a few seconds)
  • Only metadata changes (e.g. tags or pinned-state) - those go into the Activity Log, not Revisions

Browsing Revisions

The list view shows every revision sorted by date. Use the filters to narrow:

Filter What it does
Post Show every revision of one specific post or reply
User Show every revision authored by one user (across posts)
Date range Show revisions saved between two dates

Each row shows the post title, the editor, when the change was saved, and the size delta (e.g. "+128 chars / −42 chars").

Side-by-Side Diff

Revisions diff view showing the before and after versions of a post side by side, with added text highlighted green and removed text highlighted red

Click any revision to open the diff view. Pick two revisions from the same post and you'll see:

  • The before version on the left, after on the right
  • Added text highlighted green, removed text highlighted red
  • Line numbers so you can locate changes in context

The diff is character-aware, so small typo fixes show as small highlights rather than entire-paragraph rewrites.

Read-Only

Like the Activity Log, the Revisions page is read-only. To roll back to a prior revision, edit the post and paste the older content in. There's no one-click revert button - that's a deliberate choice to keep the audit trail truthful.

Permission

Only users with the jetonomy_manage_settings capability (administrators only by default) can see this page. Members can see their own edit history on each individual post, but not the cross-post Revisions admin view.

Storage

Revisions are stored in a dedicated database table separate from WordPress core revisions. They don't bloat wp_posts, and they're not affected by the WP_POST_REVISIONS constant. Jetonomy keeps every revision indefinitely; if you need a retention policy, reach out via support.

The Users admin screen gives moderators and administrators a full view of every community member - with one-click access to ban, silence, or change trust level without leaving wp-admin.

What You Will Learn

  • How to search and filter members in the admin
  • How to ban, silence, or space-ban a member
  • How to override a member's trust level manually
  • Which capability gates access to this screen

Go to Jetonomy → Users to access this screen.

Required Capability

The Users screen and its actions are gated by:

Action Required Capability
View the Users page jetonomy_manage_settings
Ban or unban a member jetonomy_moderate
Silence a member jetonomy_moderate
Change trust level jetonomy_manage_settings
Search users (AJAX picker in other admin screens) jetonomy_manage_spaces

The Users page menu is administrator-only (jetonomy_manage_settings). Editors receive jetonomy_moderate automatically, which powers the inline ban and silence actions, but it does not open the Users page menu. See Settings → Permissions → WordPress Role Mapping for the full capability table.

Searching and Filtering

The toolbar at the top of the Users screen has two controls:

  1. Search box - searches by username or display name. Type at least two characters; results update on submit.
  2. Trust Level filter - dropdown to show only members at a specific trust level (0 = Newcomer through 5 = Moderator). Select "All Trust Levels" to show everyone.

The paginator at the bottom shows total user count and lets you move between pages.

User Table Columns

Column Description
Username Avatar, login name, and row action links
Display Name Public display name
Trust Level Badge showing current level (0-5)
Reputation Total reputation score
Posts Post count
Replies Reply count
Joined Registration date (relative)
Last Seen Most recent login (relative)

Row Actions

Hover over a row to reveal the action links under the username.

View Profile

Opens the standard WordPress user edit screen for that member. This is the right place to change their email, reset their password, or modify their WordPress role.

Change Trust Level

Opens an inline dropdown directly in the table row. Choose any level from 0 (Newcomer) to 5 (Moderator) and click Save. The change takes effect immediately and is logged in the activity log.

Trust levels 0 through 3 are normally earned automatically by the trust cron job. Use this override to:

  • Promote a long-standing member to Level 4 or 5 (manual tiers)
  • Demote a member who behaved poorly but has not yet triggered enough flags for automatic demotion
  • Grant Level 2 to a known member immediately after import

Ban

Opens the Ban User modal. Three options:

Type Effect
Global Ban Member cannot log in to Jetonomy at all
Silence Member can log in and read but cannot post, reply, or vote
Space Ban Member is banned from a single space (enter the space ID in the form)

Duration options: Permanent, 1 Day, 7 Days, 30 Days.

A Reason field is optional but recommended - the reason is stored in the wp_jt_restrictions table and visible if you query restrictions via WP-CLI or the REST API.

Silence (quick action)

The row also exposes a Silence shortcut link that applies a permanent global silence immediately without opening the modal. Use the Ban modal if you want a temporary silence or a reason.

Removing a Ban

Bans are not managed through the Users screen directly after they are created. To remove a ban:

WP-CLI:

wp --path="/path/to/wordpress" jetonomy user unban <user_id>

REST API (site admin):

DELETE /jetonomy/v1/users/<user_id>/restrictions/<restriction_id>

Temporary bans (1d, 7d, 30d) expire automatically - you do not need to remove them manually.

Pausing a Member's Email

You can stop all Jetonomy email for a single member from their WordPress profile. Open the member's profile (the View Profile row action, or Users → All Users in wp-admin), scroll to the Jetonomy section, and tick Pause all Jetonomy email notifications for this user under Community emails. Save the profile.

While this is on, Jetonomy suppresses the email verification reminder and every other Jetonomy email for that member. Web notifications are unaffected - the member keeps receiving in-community notifications. This is the admin-side control for the same opt-out members can set themselves on the frontend Edit Profile page (stored as the jetonomy_email_opt_out user meta).

Trust Level Reference

Level Name How Earned
0 Newcomer Default on registration
1 Member Auto - light participation
2 Regular Auto - consistent participation
3 Trusted Auto - high engagement
4 Leader Manual only (moderator or admin)
5 Moderator Manual only (admin only)

Thresholds for levels 1-3 are configured on Settings → Permissions → Trust Level Thresholds.

What's Next?

Manage posts and replies from the content list view.

Admin Content →

The Posts and Replies admin screen lets you view, search, edit, and moderate every piece of content in your community without visiting the front end.

What You Will Learn

  • How to find and filter community posts and replies
  • How to inline-edit a post title or body
  • How to trash, approve, or mark content as spam
  • How bulk actions work

Go to Jetonomy → Posts and Replies to access this screen.

Posts and Replies admin screen showing the content list with the status filter and an inline edit panel open

Required Capability

The Posts and Replies admin page itself requires jetonomy_manage_settings, which is administrator-only by default. The separate jetonomy_moderate capability (which Editors receive automatically) governs the Moderation screen, not this page.

The Toolbar

The filter bar at the top of the screen has four controls that can be combined freely:

Control What it filters
Space filter Show only posts from a specific space
Status filter All, Published, Pending, Spam, or Trash
Search box Searches post titles
Clear button Appears when any filter is active; resets all to defaults

The counter in the toolbar shows which range of results you are viewing and the total matching count.

Post Table Columns

Column Description
(checkbox) Select row for bulk actions
Title Post title, status badge, reply count link, row actions
Space The space the post belongs to
Author Display name of the author
Status Published, Pending, Spam, or Trash
Replies Reply count, linking to a filtered view of replies for that post
Views View count
Date Time elapsed since creation

Row Actions

Hover a row to reveal its action links.

Edit (inline)

Click Edit to expand an inline edit panel directly in the table row. You can change:

  • Post title (up to 255 characters)
  • Post body (full HTML content)

Click Save to apply the change. Click Cancel to collapse the panel without saving.

Trash

Moves the post to Trash status. Trashed posts are hidden from the front end but remain in the database. Use the Status filter to view trashed posts and restore them with the Restore action that appears in their row.

Spam

Marks the post as spam. Spam posts are hidden from the front end. The author's reputation is not automatically adjusted by this action.

View

Opens the post on the front end in a new tab. Only appears when the post has a valid space and slug.

Bulk Actions

To apply an action to multiple posts at once:

  1. Check the boxes next to the posts you want to affect. Check the header checkbox to select all visible rows.
  2. Choose Approve, Move to Trash, or Mark as Spam from the bulk action dropdown.
  3. Click Apply.

A confirmation prompt appears before the action runs. The spinner in the toolbar indicates the request is in flight.

Tip: If a bulk action partially fails (some rows succeed, some do not), the page will report the count that succeeded. Refresh and re-apply to the remaining rows.

Viewing Replies

Clicking the reply count number for any post opens a filtered view showing only the replies for that post. Replies use the same toolbar, status filter, and inline-edit actions as posts.

Status Definitions

Status Meaning
Published Visible to all users with read access
Pending Created by a member at trust level 0 and awaiting first-post review
Spam Flagged or manually marked as spam
Trash Soft-deleted; not visible on the front end

Note: Pending posts are held for review when the anti-spam settings require first-post moderation. Once you approve a member's first post, subsequent posts from that member publish immediately.

What's Next?

Organize your community's spaces into categories.

Admin Categories →

Categories let you group related spaces under a shared label so members can browse a subset of your community without seeing everything at once.

This is the admin reference for the Categories screen. For how categories fit alongside spaces in your community structure, see Categories in the Spaces & Categories guide.

What You Will Learn

  • How to create, edit, and delete categories
  • How to open a category on the front end from the list
  • How category visibility works
  • How to reorder categories with drag and drop
  • How icons and colors work

Go to Jetonomy → Categories to access this screen.

Required Capability

The Categories admin page requires jetonomy_manage_settings, which is administrator-only by default. Both jetonomy_manage_settings and jetonomy_manage_spaces are granted only to administrators - Editors do not receive either capability, so they cannot open this page.

Page Layout

The Categories screen is split into two panels side by side:

  • Left - Add New Category form for creating a new category
  • Right - Categories table listing existing categories and their children

Categories admin screen showing the two-panel layout: the Add New Category form on the left and the drag-to-reorder categories table on the right

Creating a Category

Fill in the Add New Category form and click Add Category.

Field Required Notes
Name Yes Displayed in navigation and on the category page
Slug No Auto-generated from the name if left blank. Used in URLs.
Description No Optional text shown on the category listing page
Parent Category No Nest this category under an existing one. Two levels max.
Visibility No Controls who can see this category (see Visibility section below)
Icon No Choose from the Lucide icon picker
Color No Color swatch displayed next to the category name in navigation

Visibility Options

Option Who can see the category
Public All visitors (including logged-out) when guest access is on
Private Logged-in members only
Hidden Not shown in navigation or listings; direct URL still works

The category's visibility does not override the visibility of individual spaces within it. A public category can contain private spaces.

Editing a Category

Click Edit in the row actions under any category name. An Edit Category modal opens with the same fields as the creation form. Make your changes and click Update Category.

Viewing a Category on the Front End

Each row shows the category's full front-end path in the Slug column - the complete address such as /community/category/announcements/, not just the bare slug. A View link in the row actions opens that page in a new tab.

Since Jetonomy 1.6.0 the list surfaces this path and View action directly, so you no longer have to guess the address from the slug and risk a 404. Use View to confirm a category renders as expected right after creating or editing it.

Deleting a Category

Click Delete in the row actions. A browser confirmation prompt appears before the delete runs.

Warning: Deleting a category does not delete the spaces inside it. Spaces that belonged to the deleted category lose their category assignment and move to "uncategorized." There is no undo - confirm you have reassigned or are okay losing the grouping before deleting.

Reordering Categories

Drag the handle icon at the far left of any row to reorder categories. The order is saved automatically when you drop the row. Child categories follow their parent when the parent moves.

Child Categories

Set Parent Category when creating or editing a category to nest it under an existing top-level category. The table renders children indented below their parent. Two nesting levels are supported.

Use the search box in the table toolbar to filter categories by name. Clear the search to see all categories again. The search does not affect the Add New Category form.

Rows Per Page

The dropdown in the table toolbar controls how many categories appear per page (20, 50, or 100). The selection submits the form immediately.

What's Next?

Enable or disable Jetonomy Pro extensions if you have the Pro plugin active.

Extensions →

The Extensions screen is your control panel for turning individual Jetonomy Pro features on or off. It appears in the Jetonomy menu only when Jetonomy Pro is active.

What You Will Learn

  • Where the Extensions screen lives and who can access it
  • How to enable or disable an extension
  • What happens to data when you disable an extension

Go to Jetonomy → Extensions to access this screen. This menu item is added by Jetonomy Pro and is not visible on free-only installs.

Extensions screen showing the Pro extension cards in a grid with category filters and on/off toggles

Required Capability

The Extensions screen is gated by jetonomy_manage_settings, which is administrator-only by default. Only WordPress Administrators can open the screen and enable or disable extensions.

The Extension Grid

Extensions are displayed as cards in a grid. Each card shows:

  • Extension name and category tag
  • Version number
  • Short description of what the extension does
  • Toggle switch (on = enabled, off = disabled)

Active extensions have a highlighted card border. Inactive extensions appear muted.

Filtering by Category

Use the filter buttons above the grid to show only extensions in a specific category. Available categories include Communication, Engagement, Administration, Moderation, Gamification, Content, Integration, Branding, SEO, Privacy, and AI.

Click All to return to the full list.

Enabling an Extension

Click the toggle switch on the card. The page reloads and the extension is now active. On first enable, the extension's activate() method runs - this creates any database tables the extension needs and schedules any cron hooks.

Disabling an Extension

Click the toggle switch on an active card. The page reloads and the extension is now off. The extension's deactivate() method runs - this removes scheduled cron hooks and deregisters rewrite rules.

Note: Disabling an extension does not delete its data. Database tables and stored options are preserved. Re-enabling the extension restores full functionality with all existing data intact.

Available Extensions

Extension Category What It Adds
Private Messaging Communication Direct and group message threads at /community/messages/
Analytics Administration Engagement graphs, top spaces, top contributors
Emoji Reactions Engagement Per-post and per-reply emoji reaction picker
Polls Engagement Inline polls inside posts
Custom Badges Gamification Award and display custom achievement badges
Custom Fields Content Add custom profile and post fields
Webhooks Integration Outgoing webhooks on community events
Advanced Moderation Moderation Keyword rules, regex patterns, auto-action on matched content
White Label Branding Replace Jetonomy branding with your own
Email Digest Communication Daily and weekly summary emails for members
Web Push Communication Browser push notifications
SEO Pro SEO Schema.org markup, sitemaps, canonical handling
Reply By Email Communication Members reply to threads by replying to notification emails
AI Integration AI Spam detection, post summarization, semantic search, content suggestions
Site Announcements Moderation Pin a post to the top of every space for site-wide updates
Anonymous Posting Privacy Let members post and reply without showing their name, with an audited admin-only reveal
File Attachments Content Attach images, PDFs, and documents to topics and replies

Note: As of 1.8.0, attachments already on a post (from an import, the mobile app, or the REST API) display in the free plugin whether or not this extension is enabled. Enabling File Attachments here adds the composer, the lightbox, the inline PDF viewer, and configurable size/type limits - and keeps working if you disable the extension later, since the underlying files are never hidden.

For detailed setup instructions for each extension, see the Pro Features documentation.

What's Next?

For the full feature documentation for each Pro extension, see the Pro Features section.

Pro Features →

The License tab is where you activate your Jetonomy Pro license key. Activating unlocks every Pro extension and turns on automatic updates. This tab appears only when Jetonomy Pro is installed and active.

What You Will Learn

  • Where to find the License tab and what it does
  • How to activate your license key
  • How the license unlocks Pro extensions
  • What happens when a license is missing, expired, or deactivated

Go to Jetonomy → Settings → License to open this screen. The tab is added by Jetonomy Pro and is not present on free-only installs.

Activating Your License

  1. Open Jetonomy → Settings → License.
  2. Paste your license key into the License Key field. You receive this key in your purchase receipt and in your store account.
  3. Click Activate License.

On success you see a "License activated successfully" confirmation, and the screen switches to a status view showing your masked key and the expiry date (or "Never (Lifetime License)" for lifetime plans).

How the License Unlocks Extensions

A valid license unlocks all Pro extensions - there is no per-feature or per-tier gating once the key is active. Each extension only runs when two conditions are both true:

  1. The extension is switched on under Jetonomy → Extensions.
  2. Your license is currently valid.

If the license is missing or expired, enabled extensions stop booting and their features (private messaging, polls, reactions, analytics, and so on) become unavailable, even though their toggles still show on the Extensions screen. Re-activating a valid key restores them immediately - your data is never deleted.

Updates

The license also powers automatic updates. With a valid key, Jetonomy Pro receives new versions through the normal WordPress Updates screen. Without an active license you can keep using the version you have, but you will not be offered Pro updates.

Deactivating a License

To move your license to a different site, click Deactivate License on the status view. This frees the activation slot so you can activate the same key elsewhere. Deactivating removes the stored key from this site and stops Pro updates and extensions until a valid key is activated again.

Troubleshooting

  • "License is not active for this site." - The key is valid but its activation limit is reached, or it is registered to a different site. Deactivate it on the other site, or check your plan's site limit.
  • Expired license - Renew from your store account, then re-activate. Extensions resume the moment a valid key is in place.
  • Key not accepted - Confirm you pasted the full key with no trailing spaces, and that you are using the Pro license key (not an order number).

What's Next?

Turn individual Pro features on or off from the Extensions screen.

Extensions →

The Integrations tab connects Jetonomy to companion plugins and keeps Jetonomy and BuddyPress group activity in sync. The tab is always available under Jetonomy → Settings; the BuddyPress sync card described on this page appears inside it only when BuddyPress with the Groups component is active.

What You Will Learn

  • That the Integrations tab is always available, and when the BuddyPress card appears inside it
  • What the two BuddyPress sync toggles do
  • The dependency between the two toggles

Integrations tab showing the two BuddyPress sync toggles

Go to Jetonomy → Settings → Integrations to access these settings. The tab itself is always present. If you do not see the two BuddyPress toggles below, BuddyPress with the Groups component is not active - the BuddyPress card stays hidden until it is, while the rest of the Integrations tab (companion plugins) shows on every install.

Broadcast topics to group activity

Setting: jetonomy_bp_broadcast Default: On Location: Integrations tab → BuddyPress card

When on, every new Jetonomy topic created in a space that is paired with a BuddyPress group is posted into that group's activity stream. Members browsing the group see the new discussion in their activity feed without leaving BuddyPress.

Turn it off if you want Jetonomy discussions to stay inside the community and not appear in group activity.

Round-trip activity comments

Setting: jetonomy_bp_comment_bridge Default: On Location: Integrations tab → BuddyPress card

When on, comments members add to a broadcast activity item in BuddyPress are mirrored back into Jetonomy as replies on the original topic - so a conversation that starts in either place stays in sync in both.

Note: This toggle depends on Broadcast topics to group activity being enabled. If broadcast is off, there are no broadcast items for comments to round-trip, and this setting has no effect.

What's Next?

For step-by-step setup of the BuddyPress integration (pairing spaces with groups, member sync), see the integration guide.

BuddyPress Integration →

The Wbcom stack section of the Integrations tab lets you install companion plugins from the Wbcom family without leaving WordPress. Each companion adds a distinct capability alongside your Jetonomy community. Installing is one click for free companions; Pro companions require your own valid license from the store.

What You Will Learn

  • Where to find the companion installer
  • Which companion plugins are available
  • How the install and activate flow works
  • How to extend the catalog with the jetonomy_companions filter

Go to Jetonomy → Settings → Integrations to access the companion cards. The Integrations tab is always available, and the companion cards appear on every install regardless of which other plugins are active.

Available Companions

Jetonomy ships with a built-in catalog of six companions. Each card shows a status badge (Connected, Installed, or Not installed) and the action that matches that status.

WB Gamification Points, badges, and leaderboards for posts, replies, and community activity. Adds gamified recognition directly into your community alongside trust levels.

MediaVerse A social media layer with photo and video feeds alongside your community discussions. Members can post media alongside their text topics.

BuddyNext Community engine that adds profiles, activity feeds, and member spaces. Pairs with Jetonomy to give members a social home next to their discussion spaces.

Learnomy Sell and deliver online courses, then gate community spaces on course enrollment. Uses Jetonomy's built-in Learnomy access adapter so course completion unlocks spaces automatically.

WP Career Board Job listings and applicant management with employer profiles. Job posts can surface as discussion topics in your community.

Listora Member-submitted directory listings. Member listings are shared into community content alongside discussions.

How Install and Activate Works

Each companion card shows one of three states:

  • Connected - the companion is installed and active. Jetonomy detects it automatically via a capability probe, and the "what this unlocks" line appears on the card. No action is needed.
  • Installed, activate - the plugin files are on disk but the plugin is not active. A single "Activate" button activates it without leaving the settings screen.
  • Not installed - the plugin is not on disk. An "Install free" button triggers the one-click install.

When you click "Install free", Jetonomy posts to the store at wbcomdesigns.com using the companion's built-in free distribution key, retrieves the signed download URL, hands it to WordPress's own plugin installer, and then activates the plugin in one flow. You are redirected back to the Integrations tab with a success or error notice. Free companions use a baked-in key and do not require you to enter any license. Pro companions are not listed here with an install button; their product page link opens the store so you can purchase and install manually.

Once a companion is active, Jetonomy's integration code detects it on every load and enables the matching features. Deactivating a companion from the standard Plugins screen returns those features to their unconnected state; no Jetonomy data is removed.

Extending the Catalog

The companion catalog is filterable. Pro plugins and third-party integrations can add their own entries:

add_filter( 'jetonomy_companions', function( array $companions ): array {
    $companions['my-plugin'] = [
        'label'     => 'My Plugin',
        'why'       => 'Short description of what it adds.',
        'detect'    => static fn() => defined( 'MY_PLUGIN_VERSION' ),
        'free'      => [
            'item_id'  => 0,      // EDD item ID; 0 disables one-click install.
            'key'      => '',
            'basename' => 'my-plugin/my-plugin.php',
        ],
        'store_url' => 'https://example.com/my-plugin/',
        'unlocks'   => 'What lights up in Jetonomy when this is active.',
    ];
    return $companions;
} );

Set item_id to 0 to suppress the one-click install button and show only the "Learn more" store link. The detect callable is what Jetonomy uses to determine whether the companion is active - it should return true when the companion's capability is live.

When members upload images inside Jetonomy - in post bodies, reply bodies, or space cover images - those files land in the standard WordPress media library. On an active community this can flood the site owner's own media with thousands of member uploads.

Jetonomy 1.5.0 introduces Community Media: a dedicated admin view that tags member uploads and keeps them separate from the site owner's media, without moving any files or breaking third-party storage or image optimization plugins.

Where to Find Community Media

Community Media is available to site administrators and users with the jetonomy_manage_settings capability at:

WordPress admin → Jetonomy → Community Media

What It Shows

The Community Media page lists every upload made by community members through Jetonomy, paginated in a 24-item grid (newest first by default). You can filter the list by:

  • Space - see all uploads made inside a specific space
  • Member - type a username or email to see uploads from one person
  • Sort order - switch between most recent and oldest first

Each item in the grid is a standard WordPress attachment. Clicking one opens its details in the media modal, the same as clicking any attachment in the main media library.

Effect on the Main Media Library

By default, Jetonomy hides community uploads from Media → Library and from the media modal that appears when editing posts or pages. This keeps the site owner's own images, logos, and assets uncluttered.

A Community uploads dropdown on the media list toolbar lets you reveal member uploads on demand:

  • Hide community uploads (default) - community uploads are excluded from the list and the modal
  • Show community uploads - all uploads are visible

The choice in the list view is applied per-request via a URL parameter. The choice in the grid view (the media modal) is persisted in a browser cookie until changed.

Note: Hiding community uploads in wp-admin does not affect front-end delivery. Images uploaded by members are always served at their original URLs regardless of this setting.

What's Next?

Connect Jetonomy to your membership, LMS, and CRM tools.

Integrations →

Migration

Import from bbPress, wpForo, and other forum plugins.

Bringing an existing forum into Jetonomy? This page covers everything that is the same across all three importers - which source to pick, how to read the import screen, and the checklist to run after any import. Then follow the guide for your specific forum software.

Jetonomy Import screen showing detected forum sources with stat previews and Import buttons

Which Importer Do I Need?

Jetonomy ships with three built-in importers. Pick the one that matches the forum plugin you are moving away from:

You are coming from Use this guide
bbPress Importing from bbPress
wpForo Importing from wpForo
Asgaros Forum Importing from Asgaros Forum

These three are the only built-in sources. Developers can add support for other forum software through the jetonomy_importers filter - see the developer reference for details.

Before Any Import: Back Up

Always take a full database backup before importing, no matter which source you use. The importers read from your old forum's tables and never modify them, but importing creates new records in Jetonomy and cannot be automatically undone. A backup is your safety net if you want to start fresh.

Keep your old forum plugin (bbPress, wpForo, or Asgaros) active during the import - each importer reads directly from that plugin's live tables. You can deactivate it once you have confirmed the import looks right.

Browser or WP-CLI?

You can run any import two ways. Use this to decide:

  • Browser (Jetonomy → Import) - the simplest option, with a live progress bar. Best for small to medium communities (under roughly 50,000 topics + replies). The risk on large databases is a browser or server timeout part-way through.

  • WP-CLI (command line) - the reliable option for large communities, because it is not subject to browser timeouts. Run it from your server's command line (SSH). The valid source values are bbpress, wpforo, and asgaros (all lowercase):

    wp jetonomy import bbpress
    wp jetonomy import wpforo
    wp jetonomy import asgaros
    

    If you run the command on a managed host where you must point at the WordPress install, add --path:

    wp --path="/path/to/wordpress" jetonomy import bbpress
    

    If you type a source name that does not exist, the command lists the valid ones back to you.

Attachments and Inline Images

As of Jetonomy 1.8.0, all three importers bring over more than text. Images embedded in a topic or reply, and files attached to it, are downloaded from your old forum and registered into the WordPress media library as part of the same import run - members do not need to re-upload anything, and the files keep working even if you later remove the old forum plugin's upload folder.

If a specific file cannot be recovered (for example, it is already missing from disk), the import does not fail because of it. It finishes normally and reports how many files it could not recover, for example: "3 files could not be recovered and were left linked in the original post text." Treat that message as a to-do list for a handful of posts, not as a failed import - everything else has already come across.

Reading the Import Screen

When you open Jetonomy → Import, each forum plugin that Jetonomy detects appears as its own card. Here is what every part of the card means:

  • No Forum Data Detected - if none of bbPress, wpForo, or Asgaros is installed with content, you see this empty state instead of cards. Install and add content to one of those plugins, then return.
  • Stat preview - each detected source shows a live count of what it found (for example Forums, Topics, Replies). This is read straight from your old forum so you can confirm Jetonomy sees your data before you start.
  • Status badge - one badge per card tells you the card's state:
    • Available - detected and ready to import; this is the normal first-time state.
    • Previously Imported - you have already run this import once. The card shows the date of the last import and how many records it brought over.
    • Import Interrupted - a browser import stopped before finishing. The card offers Resume Import to continue, or Start Over to begin again.
  • Re-Import warning - once a source shows Previously Imported, its button changes to Re-Import and the card warns that re-importing may create duplicate content. Clicking it asks you to confirm first. Only re-import if the previous import had a problem - running it a second time on top of a successful import will duplicate your topics and replies.
  • Progress tracker - while an import runs, a five-step tracker shows where it is: Forums → Topics → Replies → Profiles → Finalize, with a percentage progress bar underneath.

After Any Import

These steps apply to every source. The individual guides list the same checklist with source-specific notes, but the essentials are:

  • Visit your community home and confirm your spaces match your old forums.
  • Open several posts and confirm the content and replies came across intact.
  • Re-assign moderators. No importer brings over moderator assignments - set Space Moderator roles manually under Jetonomy → Spaces.
  • Flush permalinks if spaces 404. Go to Jetonomy → Settings → Permalinks and click Save. (The bbPress importer does this for you automatically; wpForo and Asgaros do not, so do it by hand if new spaces return a 404.)
  • Clean up old shortcodes. If your pages or widgets used your old forum's shortcodes, remove or replace them - they will print raw shortcode text while the old plugin is still active.
  • Once everything checks out, you can deactivate the old forum plugin.
  • If the import reported files it could not recover, open those specific posts and re-attach or re-upload the file by hand.

What's Next?

Ready to import? Start with the guide for your forum software:

Move your existing bbPress community into Jetonomy - forums, topics, replies, user data, and vote history - using the built-in importer.

Import tool interface with source selection and progress tracking

New to migration? Read the Migration overview first - it explains how to read the import screen (stat previews, status badges, the progress tracker) and the backup rule that applies to every import.

What You Will Learn

  • What data the bbPress importer brings over and what it leaves behind
  • How to prepare your site before running the import
  • How to start the import, monitor progress, and resume if it stops
  • How to use the CLI dry-run option to validate before writing data
  • What to verify after the import completes

What Gets Imported

bbPress Data Imported As Notes
Forums Jetonomy Spaces Forum description → space description
Topics Jetonomy Posts Topic title + content preserved
Replies Jetonomy Replies Imported as flat replies on the post (bbPress reply threading is flattened)
User accounts Linked to existing WP users Matched by user ID
Sticky topics Pinned posts Preserved
Inline images and attached files (1.8.0+) WordPress media library + Jetonomy attachments Downloaded from bbPress and re-registered as attachments; images stay inline, other files show as a download link

Not imported:

  • bbPress topic tags
  • bbPress user activity counts / reputation
  • bbPress votes (no standard bbPress vote data is read)
  • Forum moderator assignments (assign Space Moderator roles manually after import)
  • bbPress subscriptions (replaced by Jetonomy follow/subscribe)
  • bbPress private messages (import to Jetonomy Pro private messaging separately)
  • Custom bbPress meta fields (use the jetonomy_importers filter to extend)
  • Forum avatars (WordPress avatars carry over via Gravatar/WP user accounts)

Attachments (1.8.0+): If a file cannot be recovered - for example it is already missing from disk - the import still completes and tells you how many files it could not bring over ("N files could not be recovered and were left linked in the original post text"). That is a warning to check a handful of posts by hand, not a failed import. See the Migration overview for details.

What to do about the gaps: Jetonomy replies are flat by design - every reply attaches to the topic, not to another reply - so the conversation stays intact even though bbPress's nested threading is not carried over. Topic tags are not imported; if tags matter to you, re-tag your highest-value topics by hand after the import (it is usually a small number that drive most of the traffic).

Pre-Import Checklist

Complete these steps before starting the import:

  1. Back up your database. The importer does not modify bbPress tables, but a backup is essential.
  2. Activate Jetonomy and complete the setup wizard. Your community base URL should be set.
  3. Keep bbPress active during the import. The importer reads directly from bbPress tables.
  4. Set your server timeout high. Large imports (100,000+ records) take time. Increase max_execution_time in php.ini or use WP-CLI (recommended for large sites).
  5. Disable other heavy plugins during import if your server is resource-constrained.

Tip: For large communities (10,000+ topics), run the import via WP-CLI to avoid browser timeouts entirely. See the WP-CLI section below.

Running the Import

  1. Go to Jetonomy → Import in your WordPress admin.
  2. Select bbPress as the source.
  3. Click Start Import.

The importer processes records in batches of 500. A progress bar shows completion percentage, current batch, and estimated time remaining.

Do not close the browser tab while the import is running. If the page refreshes or you navigate away, the import will pause - but can be resumed (see below).

Dry-Run Mode

Dry-run mode is available via WP-CLI only (--dry-run). It runs the import logic without writing any data and reports the imported / skipped / error totals it would have produced:

wp jetonomy import bbpress --dry-run

The summary line looks like [DRY RUN] Import complete. Imported: 1240, Skipped: 12, Errors: 0. No database writes are made, so you can run it as many times as you need.

Estimated Import Times

Community Size Topics + Replies Estimated Time
Small Under 10,000 2 - 5 minutes
Medium 10,000 - 100,000 10 - 40 minutes
Large 100,000 - 500,000 1 - 4 hours
Very large 500,000+ Use WP-CLI

These are estimates for a typical shared hosting server. Dedicated servers will be significantly faster.

Resuming a Paused Import

If the import stops (browser closed, timeout, server restart), return to Jetonomy → Import. The card shows an Import Interrupted badge with the phase it stopped at. Click Resume Import to continue from the last completed batch, or Start Over to begin again from scratch.

You can safely resume multiple times. Records that were already imported are skipped.

Running via WP-CLI

For large communities, WP-CLI is more reliable than the browser-based importer. Run it from your server's command line (SSH); on most managed hosts you point it at the WordPress install with --path. The valid source value is bbpress (lowercase) - if you mistype it, the command lists the sources it recognizes.

wp --path="/path/to/wordpress" jetonomy import bbpress

The only flag is --dry-run:

# Dry run - validate and count without writing any data
wp jetonomy import bbpress --dry-run

WP-CLI runs without a browser timeout limit and prints an Imported / Skipped / Errors summary when the import finishes.

Post-Import Checklist

After the import completes, verify the following:

  • Navigate to your community home - spaces should match your old bbPress forums
  • Open several posts and confirm content and replies are intact
  • Check that user profiles show post counts
  • Assign Space Moderator roles to your former forum moderators (moderator assignments are not imported)
  • Test creating a new post as a regular user
  • If new spaces return a 404, visit Jetonomy → Settings → Permalinks and click Save to flush rewrite rules (normally done automatically on import completion)
  • If you used bbPress shortcodes on pages, remove or replace them - they will output raw shortcode text now that bbPress is still active

Note: After a successful import, you can deactivate bbPress. Your community data is now in Jetonomy's tables and bbPress is no longer needed.

Re-running an Import

Once bbPress has been imported, its card on Jetonomy → Import changes to a Previously Imported badge that shows the date of the last import and how many records it brought over. The Start button becomes Re-Import, and Jetonomy warns you before you proceed because re-importing creates duplicate content - it does not detect and skip what you already imported. Only re-import if the first import had a real problem; otherwise leave it alone.

What's Next?

Migrating from wpForo? The process is similar with a few wpForo-specific field mappings.

Importing from wpForo →

Move your existing wpForo community into Jetonomy - forums, topics, replies, and user profiles - using the built-in wpForo importer.

Import tool with wpForo source selected and migration progress

New to migration? Read the Migration overview first - it explains how to read the import screen (stat previews, status badges, the progress tracker) and the backup rule that applies to every import.

What You Will Learn

  • What the wpForo importer brings over and how wpForo structures map to Jetonomy
  • How to prepare your site before running the import
  • How to start and monitor the import
  • How wpForo's multi-board and forum structures map onto Jetonomy
  • What to verify after the import

What Gets Imported

wpForo Data Imported As Notes
Forums Jetonomy Spaces Forum name, slug, description preserved
Topics Jetonomy Posts Title and first post content preserved
Replies (posts) Jetonomy Replies Reply parent relationships preserved
User accounts Linked to existing WP users Matched by WP user ID
Liked posts Vote score wpForo likes mapped to upvotes
Pinned topics Pinned posts Preserved
Closed topics Closed posts Preserved
Inline images and attached files (1.8.0+) WordPress media library + Jetonomy attachments Downloaded from wpForo and re-registered as attachments; images stay inline, other files show as a download link

Not imported:

  • wpForo sub-forum hierarchy (all forums, including sub-forums, are flattened into spaces under the board's category)
  • wpForo topic tags
  • wpForo user reputation / points
  • wpForo user roles and moderator assignments (assign Space Moderator roles manually after import)
  • wpForo private conversations (different data structure; import to Jetonomy Pro private messaging separately)
  • wpForo groups and group memberships (map manually to Jetonomy spaces)
  • wpForo custom user groups beyond standard roles
  • Embedded wpForo media that uses shortcodes instead of standard <img> tags (standard <img> tags and file attachments import normally as of 1.8.0)

Attachments (1.8.0+): If a file cannot be recovered - for example it is already missing from disk - the import still completes and tells you how many files it could not bring over ("N files could not be recovered and were left linked in the original post text"). That is a warning to check a handful of posts by hand, not a failed import. See the Migration overview for details.

wpForo Data Structure Differences

wpForo and Jetonomy structure their data differently in a few key areas:

Multi-board support - wpForo lets you run multiple boards, each with its own set of forums and topics. The importer automatically detects all of your active boards and imports each one into its own Jetonomy category. Single-board installs work without any extra configuration.

Forums and categories - Within each board, wpForo nests forums inside forums. Jetonomy separates categories (top-level groups) from spaces (discussion areas), so the importer creates one Jetonomy category per board and one space per wpForo forum. Sub-forum nesting is not preserved - every forum (parent or child) becomes a flat space under the board's category.

For developers: wpForo stores each board's data in its own set of tables (wp_wpforo1_*, wp_wpforo2_*, and so on) and tracks boards in wpforo_boards. The importer reads wpforo_boards to discover boards and auto-detects the table prefix, so a custom prefix needs no configuration.

Post structure - In wpForo, the first "post" of a topic is a reply in the same table. In Jetonomy, topics and replies are separate entities. The importer promotes the first wpForo post as the Jetonomy post body and imports subsequent posts as replies.

Reputation - wpForo reputation and points are not imported. After import, trust levels are re-evaluated by the cron job based on activity within Jetonomy and your configured thresholds.

Pre-Import Checklist

  1. Back up your database. The importer reads but never modifies wpForo tables, but a backup protects against any edge cases.
  2. Activate Jetonomy and complete the setup wizard.
  3. Keep wpForo active during the import - the importer reads from wpForo's live tables.
  4. Check your wpForo table prefix. If wpForo uses a custom prefix, confirm it in wpforo_boards - the importer auto-detects it.
  5. Disable wpForo page caching if active, to avoid stale data during the import process.

Tip: If you have over 50,000 topics, use WP-CLI to run the import. Browser-based imports on large databases can time out on shared hosting.

Running the Import

  1. Go to Jetonomy → Import in your WordPress admin.
  2. Select wpForo as the source.
  3. Click Start Import.

The wpForo importer runs the entire import in a single pass - the progress bar advances in one step from start to complete. (The bbPress importer, by contrast, runs in true incremental batches.) Because it is single-pass, larger wpForo databases are best imported via WP-CLI to avoid browser timeouts.

Preview Before You Import

Always take a full database backup before you run the wpForo import - that is your way to undo if you want to start over. There is no preview mode for wpForo: only the bbPress importer supports a true --dry-run that counts records without writing them. The wpForo importer always writes data, so a backup (not a dry run) is your safety net.

Heads up: The --dry-run flag is accepted on the wp jetonomy import wpforo command, but the wpForo importer does not act on it - it performs the real import either way. Do not use it expecting a preview.

Estimated Import Times

Community Size Topics + Replies Estimated Time
Small Under 10,000 2 - 5 minutes
Medium 10,000 - 100,000 10 - 40 minutes
Large 100,000 - 500,000 1 - 4 hours
Very large 500,000+ Use WP-CLI

Resuming a Paused Import

The wpForo import runs as a single pass, so there is no mid-import resume point. If it is interrupted (timeout, server restart, or closed browser), it cannot be resumed partway through - return to Jetonomy → Import and click Start Over to run it again. For this reason, run large wpForo imports via WP-CLI, which is not subject to browser timeouts.

Running via WP-CLI

Run this from your server's command line (SSH); on most managed hosts you point it at the WordPress install with --path. The valid source value is wpforo (lowercase) - if you mistype it, the command lists the sources it recognizes.

wp --path="/path/to/wordpress" jetonomy import wpforo

WP-CLI is the recommended way to import larger wpForo databases - it is not subject to browser timeouts and prints an Imported / Skipped / Errors summary when finished.

Note: The --dry-run flag is accepted by the command but is not honored by the wpForo importer - it will still write data. Take a backup first.

Space Types After Import

All imported wpForo forums become standard Forum spaces in Jetonomy. wpForo post types (Normal, Question/Answer, Debate) are not mapped to Jetonomy space types - if you want a space to behave as Q&A, change its type in Jetonomy → Spaces after the import.

Access and Visibility After Import

The importer preserves each board's read access instead of flattening everything to public. It reads the wpForo forum's groups_can_view setting:

  • A forum that guests can read imports as a Public space with an Open join policy - anyone can find and read it.
  • A forum that was members-only (guests are not in the allow-list) imports as a Private space with an Approval Required join policy - the content stays gated and new members are vetted, exactly as it was on wpForo. A members-only board is never opened up to the public by the import.
  • When wpForo records no access signal at all, the space defaults to Public - the importer only tightens access when there is a clear restriction to preserve.

For developers: map access per site with the jetonomy_import_space_visibility filter. It receives the resolved [ visibility, join_policy ] array, the importer slug (wpforo), and the source forum row, so you can force every imported board private, whitelist specific boards to public, or apply your own rule. Any value the filter returns is validated against the allowed visibility and join-policy options before it is saved.

Post-Import Checklist

After the import completes:

  • Visit your community home and confirm spaces match your old wpForo forums
  • Open several posts from different spaces and verify content is intact
  • Assign Space Moderator roles to your former forum moderators (moderator assignments are not imported)
  • Go to Settings → Permalinks and click Save to flush rewrite rules (the wpForo importer does not flush them automatically, so this step is needed if new spaces return a 404)
  • Remove or update any wpForo shortcodes on pages and widgets
  • Consider deactivating wpForo after confirming the import - it is no longer needed

Note: If your wpForo installation used a third-party plugin for member ratings or post reactions, those values are not included in the standard import. Developers can extend the importer through the jetonomy_importers filter.

Re-running an Import

Once wpForo has been imported, its card on Jetonomy → Import changes to a Previously Imported badge showing the date and record count of the last import, and the Start button becomes Re-Import. Jetonomy warns you first because re-importing creates duplicate content - it does not skip what you already brought over. Only re-import if the first attempt had a real problem.

What's Next?

Migrating from Asgaros Forum instead? The process is similar with a few Asgaros-specific mappings.

Importing from Asgaros →

Already imported? Configure who can read your community and how members earn trust.

General Settings →

Move your existing Asgaros Forum community into Jetonomy - forums, topics, replies, and user profiles - using the built-in Asgaros importer.

Import tool with Asgaros source selected and migration progress

New to migration? Read the Migration overview first - it explains how to read the import screen (stat previews, status badges, the progress tracker) and the backup rule that applies to every import.

What You Will Learn

  • What the Asgaros importer brings over and what it leaves behind
  • How Asgaros forum hierarchy maps onto Jetonomy spaces
  • How to start and monitor the import
  • What to verify after the import

What Gets Imported

Asgaros Data Imported As Notes
Forums Jetonomy Spaces Forum name and description preserved
Sub-forums Jetonomy Sub-spaces Parent/child forum hierarchy is preserved
Topics Jetonomy Posts Topic title + first post content preserved
Replies (posts) Jetonomy Replies Imported as flat replies on the post
User accounts Linked to existing WP users Matched by WP user ID
Sticky topics Pinned posts Preserved
Closed topics Closed posts Preserved
Unapproved topics Pending posts Topics not approved in Asgaros import as pending
Inline images and attached files (1.8.0+) WordPress media library + Jetonomy attachments Downloaded from Asgaros and re-registered as attachments; images stay inline, other files show as a download link

Not imported:

  • Asgaros topic tags
  • Asgaros user reputation / points
  • Asgaros likes / reactions
  • Asgaros moderator assignments (assign Space Moderator roles manually after import)
  • Asgaros subscriptions (replaced by Jetonomy follow/subscribe)
  • Custom Asgaros meta fields (use the jetonomy_importers filter to extend)

Attachments (1.8.0+): If a file cannot be recovered - for example it is already missing from disk - the import still completes and tells you how many files it could not bring over ("N files could not be recovered and were left linked in the original post text"). That is a warning to check a handful of posts by hand, not a failed import. See the Migration overview for details.

Asgaros Data Structure Differences

Forum hierarchy - Asgaros keeps your forums and sub-forums in a single nested list. The importer creates one Jetonomy category ("Imported from Asgaros") and one space per Asgaros forum, importing parent forums before their children so your sub-forums come across as Jetonomy sub-spaces.

Post structure - In Asgaros, a topic's opening message and all its replies are stored together. The importer promotes the opening message as the Jetonomy post body and imports the remaining messages as replies.

For developers: Asgaros stores forums in one table with a parent_forum column (hence the parent-before-child import order), and stores the opening post plus all replies together in the forum_posts table.

Pre-Import Checklist

  1. Back up your database. The importer reads but never modifies Asgaros tables, but a backup protects against any edge cases.
  2. Activate Jetonomy and complete the setup wizard.
  3. Keep Asgaros Forum active during the import - the importer reads from its live tables.
  4. Disable page caching if active, to avoid stale data during the import.

Tip: For large Asgaros communities, run the import via WP-CLI to avoid browser timeouts.

Preview Before You Import

Always take a full database backup before you run the Asgaros import - that is your way to undo if you want to start over. There is no preview mode for Asgaros: only the bbPress importer supports a true --dry-run that counts records without writing them. The Asgaros importer always writes data, so a backup (not a dry run) is your safety net.

Heads up: The --dry-run flag is accepted on the wp jetonomy import asgaros command, but the Asgaros importer does not act on it - it performs the real import either way. Do not use it expecting a preview.

Running the Import

  1. Go to Jetonomy → Import in your WordPress admin.
  2. Select Asgaros Forum as the source.
  3. Click Start Import.

The Asgaros importer runs the entire import in a single pass - the progress bar advances in one step from start to complete. Because it is single-pass, it cannot be resumed partway through; if it is interrupted, click Start Over to run it again.

Estimated Import Times

Community Size Topics + Replies Estimated Time
Small Under 10,000 2 - 5 minutes
Medium 10,000 - 100,000 10 - 40 minutes
Large 100,000 - 500,000 1 - 4 hours
Very large 500,000+ Use WP-CLI

Running via WP-CLI

Run this from your server's command line (SSH); on most managed hosts you point it at the WordPress install with --path. The valid source value is asgaros (lowercase) - if you mistype it, the command lists the sources it recognizes.

wp --path="/path/to/wordpress" jetonomy import asgaros

WP-CLI is the recommended way to import larger Asgaros databases - it is not subject to browser timeouts and prints an Imported / Skipped / Errors summary when finished.

Note: The --dry-run flag is accepted by the command but is not honored by the Asgaros importer - it will still write data. Take a backup first.

Post-Import Checklist

After the import completes:

  • Visit your community home and confirm spaces match your old Asgaros forums
  • Confirm that sub-forums appear as sub-spaces under their parent
  • Open several posts from different spaces and verify content is intact
  • Assign Space Moderator roles to your former forum moderators (moderator assignments are not imported)
  • Go to Settings → Permalinks and click Save to flush rewrite rules (the Asgaros importer does not flush them automatically, so this step is needed if new spaces return a 404)
  • Consider deactivating Asgaros after confirming the import - it is no longer needed

Re-running an Import

Once Asgaros has been imported, its card on Jetonomy → Import changes to a Previously Imported badge showing the date and record count of the last import, and the Start button becomes Re-Import. Jetonomy warns you first because re-importing creates duplicate content - it does not skip what you already brought over. Only re-import if the first attempt had a real problem.

What's Next?

Now that your community is live, configure who can read it and how members earn trust.

General Settings →

Why Jetonomy

How Jetonomy compares to bbPress, wpForo, and other solutions.

Why Jetonomy - a modern, scalable discussion platform that brings forums, Q&A, idea boards, and a social feed together in one WordPress plugin.

Jetonomy community home page with modern UI and space listings

What Makes Jetonomy Different

Most WordPress forum plugins do one thing: threaded discussions. Jetonomy gives your members four discussion formats in a single community - traditional forums, Stack Overflow style Q&A with accepted answers, idea boards with a status workflow, and a social activity feed - all sharing the same members, voting, search, and moderation.

It is built WordPress-first (custom database tables, the WordPress REST API, real-time updates with no page reload, and theme colors that adapt automatically) so it scales from a handful of members to a community with tens of thousands of topics without slowing down the rest of your site.

Try a live Jetonomy community before you commit - Wbcom runs its own public support community on Jetonomy at community.wbcomdesigns.com. Browse spaces, read real support threads, and get a feel for the voting, trust-level badges, reply threading, and moderation flow on a production site.

Who Jetonomy Is For

  • Product teams who want a support forum, a Q&A knowledge base, and a public roadmap (idea board) in one place
  • Membership and course sites that need to gate discussions behind MemberPress, Paid Memberships Pro, WooCommerce, or an LMS
  • Growing communities that have outgrown a CPT-based forum and need real database performance at 10,000+ topics
  • Site owners who do not want to babysit moderation - the trust-level system promotes reliable members automatically instead of relying on hand-assigned roles
  • Anyone migrating from bbPress, wpForo, or Asgaros Forum who wants a built-in importer, not a manual re-build
  • Developers who want a documented REST API, hooks, blocks, shortcodes, and WP-CLI commands to extend the platform

Compare Jetonomy

What's Next?

How Jetonomy compares to bbPress - and why communities are switching to a modern forum experience.

Jetonomy community home page with modern UI and space listings

What You Will Learn

  • Key differences between Jetonomy and bbPress
  • Where Jetonomy excels for growing communities
  • What bbPress still does well

Try a live Jetonomy community before you commit - Wbcom runs its own public support community on Jetonomy at community.wbcomdesigns.com. Browse spaces, read real support threads, and get a feel for the voting, trust-level badges, reply threading, and moderation flow on a production site.

Feature Comparison

Feature bbPress Jetonomy
Data storage WordPress custom post types Custom database tables (20 tables)
Threaded replies 1 level 3 levels
Voting Not built-in (requires add-on) Built-in upvote/downvote
Q&A with accepted answers Not available Built-in (per-space type)
Idea boards Not available Built-in with status workflow
Trust levels Not available 6-level auto-promotion system
Full-text search WordPress default search Full-text search with filters
Real-time interactions Page reload required Real-time updates with no page reload
Moderation queue Basic Flag system + queue + auto-rules (Pro)
Anti-spam Akismet only Akismet + reCAPTCHA + Turnstile
REST API Limited 68+ endpoints (127+ with Pro)
Private messaging Not built-in Built-in (Pro)
Polls Not built-in Built-in (Pro)
Analytics Not available Dashboard with export (Pro)
Migration N/A Built-in bbPress importer

Where Jetonomy Excels

Performance at Scale

bbPress stores every topic and reply as a WordPress post. On sites with 10,000+ topics, this bloats the wp_posts and wp_postmeta tables, slowing down your entire WordPress installation - not just the forum.

Jetonomy uses its own database tables with proper indexes and cursor-based pagination. Your forum can grow to tens of thousands of topics without affecting the rest of your site.

Self-Moderating Community

bbPress relies entirely on WordPress roles. You either trust someone to moderate or you do not. There is no middle ground.

Jetonomy's trust level system automatically promotes members as they contribute. New users start restricted. Active, helpful members gradually earn the ability to edit, moderate, and manage - without any manual role changes.

A trust-level badge shown next to a member's name on a reply

Modern User Experience

bbPress was designed before mobile-first became standard. Jetonomy adapts to any theme's colors automatically, works on mobile out of the box, and offers inline editing, instant voting, and real-time notifications - all without page reloads.

Where bbPress Still Works

bbPress is a good choice if you need a simple, lightweight forum with minimal features and your community will stay small (under 1,000 topics). It has been around since 2011 and has a large ecosystem of third-party add-ons.

If you are already running bbPress and considering a switch, Jetonomy includes a built-in bbPress importer that migrates all your forums, topics, replies, and user data.

What's Next?

How Jetonomy compares to wpForo - two modern forum plugins with different approaches.

Jetonomy Q&A space showing an accepted-answer callout - a feature wpForo's basic question/answer mode does not offer

What You Will Learn

  • Key architectural differences between Jetonomy and wpForo
  • Feature comparison across both plugins
  • Which plugin fits your use case

Feature Comparison

Feature wpForo Jetonomy
Data storage Custom tables Custom tables
Forum layouts 4 built-in layouts 4 space types (forum, Q&A, ideas, feed)
Threaded replies Flat + nested option 3-level threading
Voting Likes only Upvote/downvote with reputation
Trust system Manual user groups Auto-promoting trust levels (0-5)
Q&A mode Basic question/answer Full Q&A with accepted answers
Idea boards Not built-in Built-in with status workflow
Real-time UI Page reload Real-time updates with no page reload
Search Built-in search Full-text search with advanced filters
Anti-spam reCAPTCHA v2 reCAPTCHA v3 + Turnstile (invisible)
Topic management Move topics Move + merge + split
Draft posts Not available Save as draft + scheduling
REST API Limited 68+ endpoints (127+ with Pro)
Theme integration Custom styling Inherits your theme's colors automatically (theme.json design tokens)
Membership gating Built-in groups Adapter system (MemberPress, PMPro, WooCommerce, LearnDash)
Analytics Basic stats Full dashboard with export (Pro)
Migration N/A Built-in wpForo importer

Where Jetonomy Stands Out

WordPress-Native Architecture

wpForo was originally built as a standalone forum that happens to run inside WordPress. Jetonomy was built WordPress-first - it uses the same building blocks WordPress itself uses: real-time updates with no page reload (the WordPress Interactivity API), colors that adapt to your theme automatically (theme.json design tokens), WordPress's own caching and scheduled-task systems, and the WordPress REST API as its foundation.

This means Jetonomy integrates more deeply with WordPress features like block themes, the Site Editor, and the new WordPress Abilities API (the standard way AI assistants and apps can take actions on your site).

Trust Over Roles

wpForo uses manual user groups (similar to WordPress roles). You create groups, assign permissions, and manually move users between groups.

Jetonomy automates this entirely. New members start restricted and earn trust through participation. The community moderates itself as members advance through trust levels. You configure the thresholds once and the system handles promotions automatically.

A trust-level badge shown next to a member's name on a reply

Invisible Anti-Spam

wpForo supports reCAPTCHA v2 - the "I'm not a robot" checkbox that interrupts every user. Jetonomy uses reCAPTCHA v3 and Cloudflare Turnstile, both invisible. Members never see a CAPTCHA, and trusted members (Trust Level 2+) are completely exempt.

Adapter-Based Integrations

wpForo has built-in membership gating but requires wpForo-specific extensions for each membership plugin. Jetonomy uses a universal adapter pattern - MemberPress and PMPro work out of the box in free, and Pro adds WooCommerce, LearnDash, and Restrict Content Pro. Custom adapters can be built for any membership system.

Where wpForo Works Well

wpForo is a mature product with a large installed base. It offers built-in user groups, a forum-specific SEO system, and multiple layout options. If you need a traditional forum with minimal configuration, wpForo is a solid choice.

If you are running wpForo and want to migrate, Jetonomy includes a built-in wpForo importer.

What's Next?

How Jetonomy is built to handle communities of any size - from 10 members to 100,000+.

Space page with sidebar showing topic listings and member activity

What You Will Learn

  • Why custom database tables matter for performance
  • How cursor-based pagination prevents slowdowns
  • Caching strategies that keep page loads fast
  • Real-world performance benchmarks

The Problem with CPT-Based Forums

Most WordPress forum plugins (including bbPress) store every topic and reply as a row in wp_posts, with metadata in wp_postmeta. This approach works for small forums but creates serious problems as you grow:

  • Table bloat: 10,000 topics with 50,000 replies means 60,000 extra rows in wp_posts - slowing down every WordPress query, not just forum queries.
  • Meta queries: Fetching vote counts, view counts, or sticky status requires JOIN operations against wp_postmeta - one of the slowest query patterns in WordPress.
  • No proper indexes: wp_posts was designed for blog posts, not forums. It lacks indexes for common forum queries like "sort by vote score" or "filter by space."

How Jetonomy Solves This

Custom Database Tables

Jetonomy stores all community data in 20 dedicated tables with the wp_jt_ prefix. Each table has purpose-built columns and indexes:

  • Posts table: vote_score, reply_count, view_count, last_reply_at are real columns - not meta. Sorting by popularity is a simple ORDER BY vote_score DESC with an index hit.
  • Replies table: Indexed by post_id and parent_id for fast threaded reply loading.
  • Votes table: A combined index on who voted and what they voted on means "did this user already vote?" is answered with a single fast lookup instead of a scan.

Your WordPress wp_posts table stays clean. Your forum can grow without slowing down the rest of your site.

Cursor-Based Pagination

Traditional pagination uses LIMIT/OFFSET. On page 500 of a 10,000-topic space, the database must scan and skip 9,980 rows before returning 20. This gets slower as your community grows.

Jetonomy uses cursor-based pagination: "give me 20 topics after ID 9980." The database uses the primary key index to jump directly to the right position. Page 500 loads as fast as page 1.

Smart Reply Loading

A topic with 400 replies does not load all 400 at once. Jetonomy loads the first 10 and last 10 replies, with a "load more" gap in between. Members see the opening conversation and the latest activity immediately.

When they click the gap, only the missing replies are fetched in the background - no full page reload.

Built-In Caching

Jetonomy uses WordPress object cache (wp_cache) throughout:

  • Permission checks: Cached for 60 seconds per user per space. A page with 30 replies does not run 30 permission queries.
  • Online status: Cached for 60 seconds. Showing green dots on 30 reply avatars requires 0 extra database queries.
  • Last seen tracking: Rate-limited to 1 database write per user per minute, not per page view.

If you run an object cache plugin (Redis, Memcached), Jetonomy benefits automatically.

As of 1.8.0, cached data is invalidated the instant something changes - space visibility, member counts, and profile counts all clear their cache entry immediately on write, so members never see a stale count or a space that looks visible or hidden a few minutes late.

Denormalized Counters

Jetonomy does not run COUNT(*) queries to show "42 replies" on a topic card. The reply_count column is updated incrementally when replies are created or deleted. Displaying a list of 20 topics with accurate counts requires zero extra queries.

Real-World Performance

Community Size Page Load (no cache) Page Load (Redis)
100 topics, 500 replies ~120ms ~80ms
1,000 topics, 5,000 replies ~180ms ~100ms
10,000 topics, 50,000 replies ~350ms ~150ms
50,000 topics, 200,000 replies ~500ms ~200ms

These are topic listing page loads (20 topics per page) on a standard VPS (2 CPU, 4GB RAM, SSD). Single topic pages with 30 replies load in similar times.

About these numbers: Measured on Jetonomy 1.5 with PHP 8.2, MySQL 8, and the default theme, on a 2 CPU / 4GB RAM SSD VPS. Real-world times vary with your host, theme, and other active plugins - treat these as a relative guide to how Jetonomy scales, not a guaranteed figure for your site.

Tip: For the best performance on communities with 5,000+ members, enable an object cache plugin like WP Redis or W3 Total Cache with Memcached.

What You Can Do

For Small Communities (under 1,000 members)

No special configuration needed. Jetonomy works well on shared hosting with default settings.

For Medium Communities (1,000 - 10,000 members)

  • Enable object caching (Redis or Memcached)
  • Set posts per page to 20 - 30 (default)
  • Use a CDN for static assets

For Large Communities (10,000+ members)

  • Object caching required
  • Consider a VPS or managed WordPress host
  • Monitor with Query Monitor plugin
  • Review trust level thresholds - fewer moderators means fewer permission lookups

What's Next?

How Jetonomy compares to Asgaros Forum - moving up from a lightweight forum to a full discussion platform.

Jetonomy community home page with modern spaces, voting, and trust-level badges

What You Will Learn

  • Key differences between Jetonomy and Asgaros Forum
  • Where Jetonomy excels for growing communities
  • What Asgaros Forum still does well

Try a live Jetonomy community before you commit - Wbcom runs its own public support community on Jetonomy at community.wbcomdesigns.com. Browse spaces, read real support threads, and get a feel for the voting, trust-level badges, reply threading, and moderation flow on a production site.

Feature Comparison

Feature Asgaros Forum Jetonomy
Data storage Custom tables Custom tables (20 tables)
Forum formats Forums + sub-forums 4 space types (forum, Q&A, ideas, feed)
Threaded replies Flat 3-level threading
Voting Not built-in Built-in upvote/downvote with reputation
Q&A with accepted answers Not available Built-in (per-space type)
Idea boards Not available Built-in with status workflow
Trust levels Not available 6-level auto-promotion system (0-5)
Search Built-in search FULLTEXT index with advanced filters
Real-time interactions Page reload required Real-time updates with no page reload
Moderation Approve / unapprove topics Flag system + queue + auto-rules (Pro)
Anti-spam reCAPTCHA / honeypot Akismet + reCAPTCHA v3 + Turnstile (invisible)
Membership gating Not built-in Adapter system (MemberPress, PMPro free; WooCommerce, LMS Pro)
REST API Limited 68+ endpoints (127+ with Pro)
Private messaging Not built-in Built-in (Pro)
Polls Not built-in Built-in (Pro)
Analytics Basic stats Dashboard with export (Pro)
Migration N/A Built-in Asgaros importer

Where Jetonomy Excels

More Than a Forum

Asgaros Forum is a focused, traditional forum: categories, forums, sub-forums, topics, and replies. Jetonomy gives you that same forum format plus three more - Stack Overflow style Q&A with accepted answers, idea boards with a Planned / In Progress / Shipped / Declined workflow, and a social activity feed - all in the same community with one set of members.

Self-Moderating Community

Asgaros relies on you (or assigned moderators) to approve content and watch for problems. Jetonomy's trust level system automatically promotes members as they contribute. New users start restricted; active, helpful members gradually earn the ability to edit, moderate, and manage - without any manual role changes. You set the thresholds once and the community moderates itself.

A trust-level badge shown next to a member's name on a reply

Voting and Reputation

Asgaros has no built-in voting. Jetonomy gives every topic and reply upvote/downvote controls, ranks replies by score, and turns those votes into a reputation score that drives the leaderboard and trust-level promotions.

Performance at Scale

Both plugins use their own database tables instead of WordPress posts, so both avoid the bloat that slows down CPT-based forums. Jetonomy goes further with cursor-based pagination, smart reply loading on long threads, and denormalized counters, so a space with tens of thousands of topics loads as fast as a small one. See the Scalability page for the details.

Where Asgaros Forum Still Works

Asgaros Forum is free, lightweight, and genuinely simple to set up. If you want a basic, traditional forum with no membership gating, no Q&A or idea boards, and a small-to-medium community, Asgaros is a solid, no-cost choice maintained by a dedicated developer.

If you are already running Asgaros and want the additional formats, voting, trust levels, and scalability, Jetonomy includes a built-in Asgaros importer that brings over your forums, sub-forums, topics, and replies.

What's Next?

How Jetonomy compares to other community platforms - FluentCommunity, Discourse, and BuddyBoss forums.

Jetonomy community home page with modern spaces, voting, and trust-level badges

What You Will Learn

  • How Jetonomy positions against FluentCommunity, Discourse, and BuddyBoss forums
  • Where Jetonomy fits best
  • When another platform might suit you better

Jetonomy is built WordPress-first and integrates with several of these tools rather than only competing with them. If you already run FluentCommunity, Jetonomy can coexist with it and pair spaces, and the same is true for BuddyPress / BuddyBoss communities.

Jetonomy vs FluentCommunity

FluentCommunity is a modern WordPress community plugin with courses, feeds, and spaces. It is a strong all-in-one social platform.

Jetonomy is discussion-first. Where FluentCommunity centers on a social feed and learning, Jetonomy centers on structured discussion - traditional forums, Stack Overflow style Q&A with accepted answers, and idea boards with a status workflow - on top of the same custom-table performance model. Jetonomy also adds an auto-promoting trust-level system (0-5) that moderates the community for you, built-in upvote/downvote voting with reputation, and a built-in importer path from bbPress, wpForo, and Asgaros.

If you want courses plus a social feed as the heart of your site, FluentCommunity is a natural fit. If you want a fast, structured discussion platform - forum + Q&A + ideas - that can also coexist with FluentCommunity, choose Jetonomy. The two can run side by side and share members through the FluentCommunity integration.

Jetonomy vs Discourse

Discourse is the dominant standalone (non-WordPress) discussion platform. It is powerful, but it runs as a separate application you host and maintain apart from WordPress, with its own user accounts, hosting requirements, and login.

Jetonomy gives you a comparable modern discussion experience - voting, trust levels, real-time updates, full-text search - but inside WordPress. Your members use their existing WordPress accounts, your community lives on your domain, it inherits your theme's colors automatically, and you manage it from wp-admin. There is no second server to run, no SSO bridge to maintain, and no separate billing.

Choose Discourse if you specifically want a hosted, standalone forum decoupled from WordPress. Choose Jetonomy if you want that modern experience native to your WordPress site.

Jetonomy vs BuddyBoss Forums

BuddyBoss is a social-network platform for WordPress, with member profiles, groups, and activity feeds; its forum component is built on bbPress.

Because BuddyBoss forums run on bbPress, they inherit bbPress's CPT-based storage and feature set - one level of threaded replies, no built-in voting, no Q&A or idea boards, and no trust-level automation. Jetonomy replaces that forum layer with custom-table storage, 3-level threading, voting and reputation, four space types, and auto-promoting trust levels, while still coexisting with BuddyPress / BuddyBoss for profiles, groups, and activity.

If your community is primarily a social network and the forum is secondary, BuddyBoss covers the social side well - and Jetonomy can supply the discussion layer alongside it.

Where Jetonomy Fits Best

Jetonomy is the right choice when discussion is the core of your community and you want it native to WordPress: multiple discussion formats in one place, real database performance at scale, voting and trust-level automation, and the option to coexist with the social and learning platforms above rather than replace them.

What's Next?

FAQ

Quick answers to the most common questions about Jetonomy free and Pro.

Frequently Asked Questions

Quick answers to the questions we hear most, grouped by topic. Each answer links to the full guide so you can dig deeper. Answers cover both the free Jetonomy plugin and Jetonomy Pro (the free plugin is the documentation home for both).

Getting Started

What are the server requirements? Jetonomy needs WordPress 6.7 or newer, PHP 8.1 or newer, and MySQL 5.7 or higher (or MariaDB 10.4+). See Installation.

How do I set up my first community? Activate the plugin, run the setup wizard, then create your first category and space. The Setup Wizard and Your First Community guides walk through it end to end.

Will Jetonomy match my theme? Yes. Jetonomy reads WordPress theme.json presets and adopts your theme's brand color, typography, and radius automatically, with a dedicated bridge for BuddyX, BuddyX Pro, Reign, Astra, Kadence, GeneratePress, and Blocksy. See Theme Compatibility.

Can I embed the community on a regular WordPress page? Yes. Jetonomy ships eight Gutenberg blocks and eight shortcodes, plus a "Compose Topic" block and [jetonomy_compose_topic] shortcode so members can start a discussion from any page. See Embedding the Composer and Shortcodes, Widgets & Blocks.

Spaces & Discussions

What space types can I create? Four: Forum for general discussion, Q&A for questions with an accepted answer, Ideas for roadmap-style voting, and Feed for social-style posts. See Space Types.

Can members create and edit spaces from the front-end? Yes. Space owners and moderators can create and edit a space from the front-end, including title, type, visibility, join policy, category, and the "Require moderator approval for new posts" option. See Create a Space from the Front-end and Edit a Space from the Front-end.

What is the difference between a Private space and a Hidden space? A Private space is still discoverable in listings and search, with a Join or Request to Join button, but only members can read its posts. A Hidden space appears in no listing, search, or navigation, and members reach it only by invite link. See Membership & Join Policies.

How are join requests approved? When a space uses the Approval Required join policy, requests go to space moderators and admins, who can approve or decline them from the space Members page on the community front-end or under Jetonomy -> Moderation -> Join Requests in wp-admin. Approving admits the member and notifies them by email. See Membership & Join Policies.

Does each space have an RSS feed? Every public space publishes an RSS 2.0 feed at /community/s/{slug}/feed/, auto-discovered by feed readers. Private and hidden spaces return a 404 so gated content never leaks. See Space RSS Feeds.

Can I save a draft or schedule a post? Yes. The composer lets members save a post as a draft to finish later, or schedule it to publish automatically at a future date and time. See Drafts & Scheduled Posts.

Moderation & Trust

How do trust levels work? Members move through six trust levels, 0 to 5. Levels 1 to 3 are earned automatically when a member meets configurable thresholds; levels 4 and 5 are granted manually by an admin. See Trust Levels.

How do members report bad content, and where does it go? Members flag a post or reply, and the flag lands in the moderation queue for moderators to review and act on. See Flagging & Reporting and the Moderation Queue.

Can I hold new posts for approval in a specific space? Yes. Turn on "Require moderator approval for new posts" on the space's settings, and new posts wait in the moderation queue until a moderator approves them. See Edit a Space from the Front-end.

What anti-spam protection is built in? The free plugin supports invisible CAPTCHA (Google reCAPTCHA v3 or Cloudflare Turnstile), Akismet, and rate limiting, with trusted members exempted automatically. Jetonomy Pro adds AI-based background spam review. See Anti-Spam and AI Integration.

Pro & Licensing

What is the difference between free and Pro? The free plugin delivers the full community (spaces, discussions, moderation, trust, search, integrations, mobile API). Jetonomy Pro adds extensions such as private messaging, polls, reactions, custom badges, advanced moderation, analytics, webhooks, and AI. See Getting Started with Pro.

How do I activate my license and turn on Pro extensions? Paste your key under Jetonomy -> Settings -> License and click Activate, then enable each extension you want on the Extensions tab. See Getting Started with Pro and License.

If I disable an extension, do I lose its data? No. Disabling an extension stops the feature and unregisters its hooks but preserves its tables and stored options; re-enabling restores everything. Only uninstalling the plugin removes stored data. See Extensions.

Does the AI extension lock me into one provider or a runaway bill? No. The AI layer is multi-provider (OpenAI, Anthropic, self-hosted Ollama, or any OpenAI-compatible endpoint) with a fallback chain, and every cloud provider can be given a monthly spend cap in USD that stops requests once exceeded. See AI Integration.

Mobile App

Is there a mobile app? Yes. Jetonomy has an open-source iOS and Android app built with Expo. Use it as-is or publish your own branded build from the same code. See Mobile App Overview and Get the App.

How do members sign in to the app? Through WordPress Application Passwords, a secure feature built into WordPress core, not JWT. The app password is separate from the member's real password and can be revoked at any time. See Connect Members.

Can the app send push notifications? Yes. Native push is delivered by the Pro Web Push extension, which registers each Expo device and requires Jetonomy 1.6.0 or newer and an HTTPS site. See Native Push Setup.

Integrations

Can I gate a space by membership or a purchase? Yes. Access Rules connect a space to MemberPress or Paid Memberships Pro (free), or WooCommerce and Restrict Content Pro (Pro), so access follows the member's subscription or purchase automatically. See the Integrations Overview.

Can I gate a space by course enrolment? Yes, with Jetonomy Pro. Access Rules support LearnDash, Tutor LMS, LifterLMS, Sensei LMS, MasterStudy LMS, and Learnomy (by course or membership plan), adding and removing members as their enrolment changes. See the Integrations Overview and Learnomy Integration.

Does Jetonomy work with BuddyPress? Yes. When BuddyPress Groups is active, Jetonomy pairs groups with spaces for two-way member sync and can broadcast new topics to the group activity stream; the broadcast toggle lives under Jetonomy -> Settings -> Integrations. See BuddyPress Integration.

Data & Privacy

Do I own my community data, and can I export it? Yes. Everything lives in your own database. Jetonomy registers WordPress core personal-data exporters and erasers (Tools -> Export/Erase Personal Data), and all content is reachable through the jetonomy/v1 REST API. See the REST API reference.

What does uninstalling remove? Deleting the plugin runs the uninstall routine, which removes Jetonomy Pro's database tables, Pro options, Pro user meta (every jetonomy_pro_ key), and Pro scheduled tasks. It is irreversible, so back up first. See Getting Started with Pro.

Can I permanently purge a conversation for a compliance request? Yes. Admins get a Jetonomy -> Conversations page in wp-admin where Purge permanently deletes a conversation and all its messages and read-state rows, firing an action for audit tooling. See Private Messaging.

Troubleshooting

Can I migrate from another forum plugin? Yes. Built-in importers bring topics, replies, and members across from bbPress, wpForo (including multi-board installs), and Asgaros Forum; a members-only wpForo board imports as a private space with approval to join, so gated content stays gated. See the Migration Overview.

I kept seeing "Cookie nonce is invalid" on a tab left open a long time. Is that fixed? Yes. When a long-lived tab's REST nonce expires, the bundled client fetches a fresh nonce against the still-valid login cookie and retries the request once, so members no longer lose a reply. See the REST API reference.

Developer Guide

REST API, hooks, template overrides, and extending Jetonomy.

The Developer Guide is the technical reference for extending, embedding, and integrating Jetonomy. Every page here is written for developers - if you are configuring the community from wp-admin, start with the Getting Started and Admin Settings sections instead.

Use this page as a map: each guide is grouped by what you are trying to do, so you can jump straight to the right reference no matter where you landed.

Extend

Hook into Jetonomy's data and behaviour, or swap out a back-end service.

  • Hooks Reference - every jetonomy_* action and filter, with the arguments each one passes.
  • Attachment Hooks - the 1.8.0 attachment filters (jetonomy_attachment_card, jetonomy_attachments_class, jetonomy_rest_attachment_data) and the attachments array now carried on post/reply REST payloads.
  • Adapter System - the membership, search, real-time, and email adapter interfaces you implement to plug in your own service.
  • Template Overrides - copy any community template into your theme's jetonomy/ directory to change its layout.
  • Admin Extensions - add settings tabs, space-edit tabs, moderation tabs, dashboard widgets, and menu white-label overrides to the Jetonomy wp-admin UI.

Build a companion plugin (recipes)

Step-by-step guides for the most common customization tasks.

  • Add a Profile Tab - register a new tab on the Jetonomy member profile using the jetonomy_profile_tabs filter.
  • Add a Space Tab - register a new tab on the space frontend nav using the jetonomy_space_tabs filter.
  • Add a Nav Item - add a link to the community header nav bar.
  • Customize Cards - inject content into post cards, reply cards, and the single-post view using template hooks.
  • Theming with Tokens - use the --jt-* CSS custom properties to style or rebrand the community without overriding templates.
  • Extend the Frontend - hook into the WP Interactivity API store, listen to the jetonomy:navigated event, and call restFetch from your own scripts.
  • Extend the REST API - add fields to existing responses with jetonomy_rest_prepare_* filters, and register new jetonomy/v1 routes.
  • Customize Emails - override notification email subjects, bodies, and templates using the jetonomy_email_* filter set.

Embed

Surface community content on any page, post, sidebar, or page-builder canvas.

  • Shortcodes, Widgets, and Blocks - the eight shortcodes, four classic widgets, and eight Gutenberg blocks, with attributes and block/shortcode parity notes.

Get found

Make the community discoverable to search engines and feed readers.

  • SEO and Discoverability - the structured data, Open Graph and Twitter cards, canonical URLs, XML sitemap, and per-space RSS feeds Jetonomy emits, plus the jetonomy_seo_meta filter and the Pro SEO controls.

Integrate

Read and write community data from another application, agent, or platform.

  • REST API Reference - the full jetonomy/v1 endpoint listing with methods, payloads, responses, and permission contracts.
  • OpenAPI Spec + Full REST Reference - the complete machine-readable openapi.json (load into Swagger UI / Redoc) plus a human-readable companion covering every free and Pro route, generated from 1.8.0 source.
  • Attachment Model - Jetonomy\Models\Attachment (link, get_for, get_for_many, prime_for_post, hydrate, payload_for) and the jt_attachments table.
  • Import Framework - the Jetonomy\Import\Importer base contract (media migration, error surfacing, the per-batch time budget) and how to add a source importer.
  • Abilities API - expose the community to AI agents and automation tools through the WordPress Abilities API.
  • FluentCommunity Integration - developer reference for the FluentCommunity coexistence layer.
  • BuddyPress Integration - developer reference for the BuddyPress Groups coexistence layer, including how to disable leave-sync.
  • Coming from BuddyPress / BuddyBoss - concept map, API equivalents, and an honest account of what has no Jetonomy equivalent yet.

Front-end toolkit

JavaScript and access-control building blocks for custom front-end code.

  • Modal Toolkit - the jetonomyConfirm / jetonomyAlert / jetonomyPrompt globals that replace native browser dialogs.
  • Visibility and Access Matrix - the Jetonomy\Visibility helper that enforces the public/private community toggle, plus the access-matrix regression runner.

Operations

Drive and test the community from the command line.

  • WP-CLI Commands - the full wp jetonomy and wp jetonomy-pro command surface, plus the qa-actions smoke suite.
  • Caching - the Jetonomy\Cache wrapper and the bust-after-write invalidation rule (Space::bust_cache() as the reference pattern).
  • Media Provenance - Jetonomy\Media_Library origin tagging (tag_upload, is_ours, META_ORIGIN) and why only files Jetonomy created are garbage-collected.

Jetonomy exposes a full REST API under the jetonomy/v1 namespace: 48+ endpoints in the free plugin, plus 40+ additional endpoints when Jetonomy Pro is active (90+ total). All endpoints return JSON and integrate with WordPress nonce authentication via the wp_rest nonce.

Base URL: https://example.com/wp-json/jetonomy/v1/

Authentication

Public endpoints (marked Public below) return data without authentication. Write operations and moderation endpoints require a logged-in user and the X-WP-Nonce header:

const nonce = window.wpApiSettings?.nonce
           ?? jetonomyState?.nonce;  // Injected via wp_interactivity_state()

fetch( '/wp-json/jetonomy/v1/spaces/1/posts', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'X-WP-Nonce': nonce,
    },
    body: JSON.stringify({ title: 'My topic', content: '<p>Hello</p>' }),
} );

The Interactivity API store exposes apiBase and _nonce in the jetonomy namespace so the bundled frontend needs no extra configuration.


Categories

Manage the top-level taxonomy that groups Spaces.

Method Route Auth Description
GET /categories Public List all categories
POST /categories manage_options Create a category
GET /categories/{id} Public Get a single category
PATCH /categories/{id} manage_options Update a category
DELETE /categories/{id} manage_options Delete a category

GET /categories - example

const res  = await fetch( '/wp-json/jetonomy/v1/categories' );
const data = await res.json();
// data.data → array of category objects
// { id, name, slug, description, position, space_count }

Spaces

Spaces are the primary containers for posts (equivalent to forums or boards).

Method Route Auth Description
GET /spaces Public List spaces (paginated)
POST /spaces manage_options Create a space
GET /spaces/{id} Public Get a single space
PATCH /spaces/{id} Moderator / Admin Update space settings
DELETE /spaces/{id} manage_options Delete a space
GET /spaces/{id}/members Public / Members only if private List space members
POST /spaces/{id}/members Logged in Join a space
PATCH /spaces/{id}/members/{user_id} Moderator / Admin Change a member's role
DELETE /spaces/{id}/members/{user_id} Moderator / Admin Remove a member
POST /spaces/{id}/invite Moderator / Admin Generate an invite link
GET /invite/{token} Public Resolve an invite token
GET /spaces/{id}/privileged-members Public List admins and moderators of a space

GET /spaces - parameters

Parameter Type Default Description
page int 1 Page number
per_page int 20 Results per page (max 100)
category_id int - Filter by category
search string - Search by title
orderby string position position, title, member_count, post_count
const res  = await fetch( '/wp-json/jetonomy/v1/spaces?per_page=10&category_id=3' );
const data = await res.json();
// data.data → array of space objects
// data.meta → { total, pages, page }

Viewer-relative fields (added in 1.6.0)

Every space object - in both the list and the single-space response - carries three fields that describe the calling user's relationship to the space. They are null-safe for logged-out callers (is_member and is_subscribed return false, viewer_role returns null).

Field Type Description
is_member boolean Whether the current user is a member of this space
viewer_role string | null The space role the current user holds (for example moderator or member), or null when they are not a member
is_subscribed boolean Whether the current user is subscribed to this space for new-content notifications

Posts

Posts are individual discussion threads (topics) inside a Space.

Method Route Auth Description
GET /spaces/{space_id}/posts Public List posts in a space
POST /spaces/{space_id}/posts Logged in Create a post
GET /posts/{id} Public Get a single post
PATCH /posts/{id} Author / Moderator Update a post
DELETE /posts/{id} Author / Moderator Delete a post
POST /posts/{id}/close Moderator / Admin Toggle closed status
POST /posts/{id}/pin Moderator / Admin Toggle pinned status
POST /posts/{id}/move Moderator / Admin Move to another space
POST /posts/{id}/merge Moderator / Admin Merge into another post
POST /posts/{id}/idea-status Space Moderator Set the roadmap status on an idea-type post (planned, in_progress, shipped, declined)
GET /posts/drafts Logged in List current user's drafts
GET /link-preview Public Fetch OG metadata for a URL

GET /spaces/{space_id}/posts - parameters

Parameter Type Default Description
page int 1 Page number
per_page int 20 Max 100
sort string latest latest, oldest, votes, replies
type string - Filter by post type (discussion, question, idea)
tag string - Filter by tag slug
status string publish publish, draft (author/mod only)

POST /spaces/{space_id}/posts - body

await fetch( `/wp-json/jetonomy/v1/spaces/${spaceId}/posts`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': nonce },
    body: JSON.stringify({
        title:   'How do I configure caching?',
        content: '<p>Looking for recommendations...</p>',
        type:    'question',     // discussion | question | idea
        tags:    ['caching', 'performance'],
        status:  'publish',      // or 'draft'
    }),
} );

POST /posts/{id}/move - body

{ target_space_id: 42 }

POST /posts/{id}/merge - body

{ target_post_id: 17 }

GET /link-preview - parameters

Parameter Type Required Description
url string Yes URL to fetch OG data for

Response: { title, description, image, domain }

Viewer-relative fields (added in 1.6.0)

Every post object - in the space listing, the global feed, and the single-post response - carries two fields that describe the calling user's relationship to the post. They are null-safe for logged-out callers.

Field Type Description
is_bookmarked boolean Whether the current user has bookmarked this post (false when logged out)
viewer_vote integer The current user's vote on this post: 1 (up), -1 (down), or 0 (no vote / logged out)

Replies

Replies are threaded responses to a Post.

Method Route Auth Description
GET /posts/{post_id}/replies Public List replies on a post
POST /posts/{post_id}/replies Logged in Create a reply
GET /replies/{id} Public Get a single reply
PATCH /replies/{id} Author / Moderator Update a reply
DELETE /replies/{id} Author / Moderator Delete a reply
POST /replies/{id}/accept Post author / Moderator Accept as answer
POST /replies/{id}/split Moderator / Admin Split this reply into a new standalone post

GET /posts/{post_id}/replies - parameters

Parameter Type Default Description
page int 1 Page number
per_page int 30 Max 100
sort string oldest oldest, newest, best

POST /replies/{id}/accept

Marks this reply as the accepted answer. Only the original post author or a moderator can call this. Fires the jetonomy_reply_accepted action hook and awards +15 reputation to the reply author.

await fetch( `/wp-json/jetonomy/v1/replies/${replyId}/accept`, {
    method: 'POST',
    headers: { 'X-WP-Nonce': nonce },
} );

Votes

Votes record up/down signals on Posts and Replies. Calling the endpoint again with the same direction removes the vote (toggle).

Method Route Auth Description
POST /posts/{id}/vote Logged in Cast or toggle a post vote
DELETE /posts/{id}/vote Logged in Remove vote from a post
POST /replies/{id}/vote Logged in Cast or toggle a reply vote
DELETE /replies/{id}/vote Logged in Remove vote from a reply

Body for both vote endpoints

{ direction: 'up' }  // or 'down'

Response includes vote_score (current net score) and user_vote (the caller's current vote direction or null).


Full-text search across Posts, Replies, Spaces, and Tags. Uses MySQL FULLTEXT with Boolean Mode by default. Swap to a custom search adapter (Meilisearch, Algolia, etc.) via the Adapter System. See 05-adapters.md.

Method Route Auth Description
GET /search Public Search across content types

Parameters

Parameter Type Required Default Description
q string Yes - Query string (min 2 chars)
type string - post post, reply, space, tag, all
space_id int - - Restrict to a specific space
date_from string - - ISO date YYYY-MM-DD
date_to string - - ISO date YYYY-MM-DD
author_id int - - Filter by author's WP user ID
tag string - - Filter by tag slug
sort string - relevance relevance, newest, votes

Using type=all returns a grouped response with posts, spaces, and tags keys.

const params = new URLSearchParams({
    q:        'caching strategies',
    type:     'post',
    space_id: 5,
    sort:     'votes',
} );

const res  = await fetch( `/wp-json/jetonomy/v1/search?${params}` );
const data = await res.json();
// data.data → array of matching post objects
// data.meta → { total, has_more }

Tags

Method Route Auth Description
GET /tags Public List all global tags

Removed in 1.5.0: the GET /space-tags route. It read tables that were never wired to any feature; tags have always been global. Existing integrations calling it receive a 404 and should switch to GET /tags.


Notifications

Method Route Auth Description
GET /notifications Logged in List notifications for current user
GET /notifications/unread-count Logged in Get unread count (cached 30s)
POST /notifications/mark-all-read Logged in Mark all notifications read
PATCH /notifications/{id} Logged in Mark a single notification read
DELETE /notifications/{id} Logged in (own only) Delete a single notification
POST /notifications/bulk Logged in (own only) Bulk mark-as-read or delete a batch of notifications

GET /notifications - parameters

Parameter Type Default Description
per_page int 20 Max 50
unread_only bool false Return only unread notifications

Subscriptions

Subscriptions track which Spaces or Posts a user follows for new-content notifications.

Method Route Auth Description
GET /subscriptions Logged in List current user's subscriptions
POST /subscriptions Logged in Create a subscription to a space or post
DELETE /subscriptions/{id} Logged in Remove a subscription

Target fields on each row (added in 1.8.0)

GET /subscriptions returns the raw row (id, user_id, object_type, object_id, via, created_at) plus three fields resolved from whatever the subscription points at, so a client can render the target's name instead of a row of identical links:

Field Type Description
title string The post or space title (empty string when the target is gone)
slug string The target's slug
exists boolean false when the target no longer exists - a hard-deleted post/space, or a post that has been trashed or marked spam. A pending or draft post the caller is subscribed to still returns exists=true

These are batch-loaded - two queries per page (one for posts, one for spaces), never a lookup per row.


Moderation

All moderation endpoints require the jetonomy_moderate capability (granted to admins, editors, and users with Moderator role by default).

Method Route Auth Description
GET /moderation/queue Moderator List items pending review
POST /moderation/approve/{type}/{id} Moderator Approve a flagged item
POST /moderation/spam/{type}/{id} Moderator Mark as spam
POST /moderation/trash/{type}/{id} Moderator Send to trash
POST /moderation/bulk (new in 1.4.1) Moderator Approve / spam / trash many posts in one call
POST /flags Logged in Submit a flag on a post or reply
GET /posts/{id}/flags (new in 1.4.1) Moderator The flags raised against a specific post
GET /moderation/flags Moderator List all open flags
POST /moderation/flags/{id}/resolve Moderator Resolve a flag (valid or dismissed)
POST /moderation/ban Moderator Ban a user (global ban, space ban, or silence)
DELETE /moderation/ban/{id} Moderator Remove a ban
GET /spaces/{id}/moderation/flags Space Admin List flags filed within a specific space
POST /spaces/{id}/moderation/flags/{flag_id}/resolve Space Admin Resolve a flag within a specific space
POST /spaces/{id}/moderation/{action}/{type}/{obj_id} Space Admin Moderate content in a specific space (action: approve, spam, or trash; type: post or reply)

Resolving a flag as valid applies the full resolution contract on every surface (1.5.0 fix): the flagged content is trashed, any other pending flags on the same object are cleared with it, the reporter earns +5 reputation, and the jetonomy_flag_resolved action fires (so Pro webhooks see the event). Earlier versions skipped these side effects when the flag was resolved through this global REST route specifically.

POST /moderation/bulk - body

{
    action:     'approve',  // approve | spam | trash
    object_type: 'post',    // or 'reply'
    object_ids: [101, 104, 109, 117]
}

Returns per-item results so partial failures are visible:

{
    succeeded: [101, 104, 117],
    failed:    [{ id: 109, reason: 'already_spam' }]
}

{type} in approve/spam/trash routes is either post or reply.

POST /flags - body

{
    object_type: 'post',   // or 'reply'
    object_id:   42,
    reason:      'spam',   // spam | offensive | off_topic | harassment | other
}

POST /moderation/ban - body

{
    user_id:  123,
    reason:   'Repeated spam',
    duration: 7,           // days - omit for permanent ban
}

GET /spaces/{id}/moderation/flags - parameters (1.8.0)

Brought to parity with the global /moderation/flags queue: the per-space queue now honours a status filter and paginates.

Parameter Type Default Description
status string pending pending, valid, dismissed, all
limit int 20 Page size
offset int 0 Offset into the result set

The response meta carries total, has_more, count, offset, and space_id; total and has_more reflect the requested status, not just the default. Each flag row includes a resolved reporter object (id, display_name, user_login, avatar_url) plus resolved_by_name, so the queue can name who filed and who resolved a report. Reporters are batch-loaded (one lookup per page, not per row).

Dev note (1.8.0): the shared paginated_response() now raises a _doing_it_wrong() when a route paginates (item count < total) but passes no offset - guarding against a "Load more" that never ends. It fires only for the genuinely-paginated, offset-missing case.


Leaderboards

Method Route Auth Description
GET /leaderboards Public Get top contributors

Parameters

Parameter Type Default Description
period string week week, month, all-time
per_page int 10 Max 50
space_id int - Restrict to a space

Users

Method Route Auth Description
GET /users/me Logged in Get the current user's profile
PATCH /users/me Logged in (own account) Update the current user's profile
GET /users/{id} Public Get a user's public profile
PATCH /users/{id} Owner / Admin Update a user profile
GET /users/by-login/{login} Public Look up a user by login slug
GET /users/{id}/posts Public List posts by this user
GET /users/suggest Public Typeahead - suggest users by name or login prefix

PATCH /users/{id} - updatable fields

{
    bio:         'Forum moderator and PHP developer.',
    website:     'https://example.com',
    location:    'Berlin',
    twitter:     'janedoe',
    github:      'janedoe',
    avatar_url:  'https://example.com/avatar.jpg',
}

avatar_display (added in 1.7.0)

Every user object returned by /users/me, /users/{id}, and /users/by-login/{login} carries a read-only avatar_display field: the resolved URL a client should render. It is the member's avatar_url when set, otherwise the best available real avatar (uploaded, BuddyPress, or a hosted Gravatar). It is an empty string '' when none of those exist - the signal for the client to render a generated initials avatar instead of a blank placeholder. avatar_url remains the writable field on PATCH; avatar_display is compute-only.


Updates (Polling)

The Updates endpoint powers the "N new replies" banner in single-post view. It is polled periodically by the Interactivity API store.

Method Route Auth Description
GET /updates Public Check for new activity since a timestamp

Parameters

Parameter Type Description
since string ISO 8601 timestamp. Returns items created after this time.
post_id int If provided, returns new reply count for that post

Bookmarks

Method Route Auth Description
GET /bookmarks Logged in List the current user's bookmarked posts
POST /bookmarks Logged in Toggle a bookmark on a post (adds if absent, removes if present)
DELETE /bookmarks/{post_id} Logged in Remove a specific bookmark by post ID

Media

Method Route Auth Description
POST /media Logged in (jetonomy_upload_media) Upload an image, video, or file attachment

oEmbed

Method Route Auth Description
GET /oembed Public oEmbed endpoint for forum posts - returns embed metadata for the given post URL

Authentication and Registration

These endpoints are unauthenticated and rate-limited. They are used by the headless frontend or when the native WordPress login form is not in use.

Method Route Auth Description
POST /auth/login Public (rate limited) Log in and receive an authentication cookie
GET /auth/nonce (new in 1.5.0) Logged in (cookie) Mint a fresh REST nonce for the current session
POST /auth/register Public (rate limited) Register a new user account
POST /auth/lost-password Public (rate limited) Request a password reset email
GET /auth/verify-email Public Verify an email confirmation token
POST /auth/resend-verification Public (rate limited) Resend the email verification message

GET /auth/nonce backs the frontend's automatic session recovery: when a long-lived tab's REST nonce expires (403 rest_cookie_invalid_nonce), the bundled restFetch client calls this endpoint, receives a fresh nonce minted against the still-valid login cookie, and retries the original request once - so members never lose a reply to "Cookie nonce is invalid". The endpoint re-validates the login cookie itself and sends no-cache headers; it never mints a nonce for an anonymous session.


Admin

These endpoints require the manage_options capability (administrators only).

Method Route Auth Description
POST /admin/recount Admin (manage_options) Rebuild all denormalized counters (reply counts, vote scores, post counts)
POST /admin/users/trust-level Admin (manage_options) Manually set a user's trust level

Mobile App

Endpoints that power the Jetonomy mobile app. Added in 1.6.0.

Method Route Auth Description
GET /app/config Public Per-site branding + feature flags the app reads on connect
GET /feed Public Global feed across every space the caller can see
POST /push/register-device Logged in Register this device's Expo push token for native push
DELETE /push/register-device Logged in Unregister the device's push token

GET /app/config

Public - the app reads it on connect to theme itself per site before a user signs in. app_name comes from Settings -> General -> Community Title (falling back to the site name). space_label carries the singular and plural of the configurable "Space" noun so the app labels match the web. accent_color, logo_url, and login_bg_url come from branding: the Pro white-label row when Pro is active, otherwise the free Settings -> Appearance values. The features map reflects which Pro extensions are active, so the app gates its UI on them.

app_enabled is the fail-closed gate that decides whether the mobile app signs in at all. It defaults to false in the free plugin. Jetonomy Pro flips it to true through the jetonomy_app_config filter, and only when the site holds a valid Pro license. When it is false, the app shows a "requires Jetonomy Pro" screen and refuses to sign in, so the app never runs against a free-only or unlicensed install.

Dark mode is not part of this payload - the app follows the device/OS theme.

{
  "app_name":     "Course Academy",
  "space_label":  { "singular": "Space", "plural": "Spaces" },
  "accent_color": "#7C3AED",
  "logo_url":     "https://example.com/logo.png",
  "login_bg_url": "",
  "pro_active":   true,
  "app_enabled":  true,
  "features": {
    "messaging":     true,
    "reactions":     true,
    "polls":         true,
    "badges":        true,
    "custom_fields": true,
    "web_push":      true,
    "native_push":   true
  }
}

GET /feed - parameters

A single cross-space feed (the app's Home tab). Returns only posts in spaces the caller may view. The feed is offset-paginated: pass limit and offset. It does not honour the generic after/before cursor params - they are inert on this route.

Parameter Type Default Description
sort string hot hot, new, top
limit int - Page size (max 50)
offset int 0 Offset into the result set
window_days int 7 For sort=top, the look-back window in days (0 = all-time)

POST /push/register-device - body

Registers the device for native (Expo) push. DELETE with the same expo_push_token removes it.

{
    expo_push_token: 'ExponentPushToken[xxxxxxxx]',  // required
    platform:        'ios',                           // required: ios | android
    device_name:     'My iPhone',                     // optional
}

Branding is set from wp-admin - see the Mobile App docs for the site-owner walkthrough.


Pro Endpoints

The following endpoints are available only when Jetonomy Pro is active and the relevant extension is enabled.

Private Messaging (private-messaging extension)

Method Route Auth Description
GET /conversations Logged in List conversations
POST /conversations Trust Level 1+ Start a new conversation
GET /conversations/{id} Participant Get conversation details
PATCH /conversations/{id} Participant Update conversation settings (is_muted boolean); see also the dedicated POST /conversations/{id}/mute
GET /conversations/{id}/messages Participant List messages (paginated)
POST /conversations/{id}/messages Participant + TL 1+ Send a message
POST /conversations/{id}/mute Participant Mute/unmute the conversation (muted boolean)
POST /conversations/{id}/archive Participant Archive/unarchive the conversation for the caller (archived boolean)
POST /conversations/{id}/leave Participant Leave a group conversation
POST /conversations/{id}/block Participant Block/unblock the other participant (blocked boolean)
GET /conversations/unread-count Logged in Unread message count (30s cache)
GET /messaging/recipient-suggestions Logged in Typeahead for the DM composer, scoped to shared-space members (q required, 3-64 chars)

POST /conversations - body

{
    participants: [4, 17],          // WP user IDs
    title:        'Project sync',   // Optional for group conversations
    message:      'Hey, quick question...',
}

Analytics (analytics extension)

All analytics endpoints require the jetonomy_view_analytics capability.

Method Route Description
GET /analytics/overview Daily series + period comparison
GET /analytics/top-spaces Ranked by period activity
GET /analytics/top-contributors Ranked by posts + replies
GET /analytics/engagement Engagement rate, avg reply time, unanswered %
GET /analytics/moderation Flags, bans, spam actions
GET /analytics/export CSV download

Analytics parameters (all endpoints)

Parameter Type Default Description
range string 30d 7d, 30d, 90d, custom
start string - ISO date (required when range=custom)
end string - ISO date (required when range=custom)
const res  = await fetch(
    '/wp-json/jetonomy/v1/analytics/overview?range=30d',
    { headers: { 'X-WP-Nonce': nonce } }
);
const data = await res.json();

Polls (polls extension)

Method Route Auth Description
GET /posts/{post_id}/poll Public Get a post's poll and current results
POST /posts/{post_id}/poll Logged in (can post) Create a poll on a post
POST /polls/{id}/vote Logged in Cast a vote on a poll option
DELETE /polls/{id}/vote Logged in Retract a vote
PATCH /polls/{id} Author / Moderator Update or close a poll

Reactions (reactions extension)

Method Route Auth Description
GET /posts/{id}/reactions Public List reactions on a post
POST /posts/{id}/reactions Logged in (jetonomy_vote) Toggle an emoji reaction on a post
GET /replies/{id}/reactions Public List reactions on a reply
POST /replies/{id}/reactions Logged in (jetonomy_vote) Toggle an emoji reaction on a reply

Custom Badges (custom-badges extension)

Method Route Auth Description
GET /badges Public List all defined badges
POST /badges Admin (manage_options) Create a new badge definition
GET /badges/{id} Public Get a single badge with its earned count
PATCH /badges/{id} Admin (manage_options) Update a badge definition
DELETE /badges/{id} Admin (manage_options) Delete (deactivate) a badge definition
GET /users/{id}/badges Public List the badges a user has earned
POST /badges/{id}/award Admin (manage_options) Manually award the badge to a user (user_id in body)
DELETE /badges/{id}/award Admin (manage_options) Revoke the badge from a user (user_id in body)

Custom Fields (custom-fields extension)

Method Route Auth Description
GET /fields Public List all custom field definitions
POST /fields Admin (manage_options) Create a custom field definition
PATCH /fields/{id} Admin (manage_options) Update a custom field definition
DELETE /fields/{id} Admin (manage_options) Delete a custom field definition
GET /posts/{id}/fields Public Get a post's custom field values
PATCH /posts/{id}/fields Logged in (author / moderator) Set custom field values on a post
GET /users/{id}/fields Public Get a user's custom field values
PATCH /users/me/fields Logged in Set the current user's custom field values

Email Digest (email-digest extension)

Method Route Auth Description
GET /users/me/digest-preferences Logged in Get the current user's digest frequency and topic preferences
PATCH /users/me/digest-preferences Logged in Update digest frequency and topics
POST /admin/digest/test manage_options Send a test digest email
GET /admin/digest/stats manage_options Digest delivery statistics

Webhooks (webhooks extension)

Method Route Auth Description
GET /webhooks Admin (manage_options) List all registered webhook endpoints
POST /webhooks Admin (manage_options) Register a new webhook endpoint
PATCH /webhooks/{id} Admin (manage_options) Update a webhook endpoint
DELETE /webhooks/{id} Admin (manage_options) Delete a webhook endpoint
POST /webhooks/{id}/test Admin (manage_options) Send a test delivery to the endpoint
GET /webhooks/{id}/deliveries Admin (manage_options) List recent delivery attempts for the endpoint

White Label (white-label extension)

Method Route Auth Description
GET /settings/white-label Admin (manage_options) Get current white-label settings (logo, colors, footer text)
PATCH /settings/white-label Admin (manage_options) Save white-label settings

AI (ai extension)

Method Route Auth Description
GET /ai/usage Admin (manage_options) Get per-request AI usage statistics
GET /ai/usage/summary Admin (manage_options) Get monthly usage summary grouped by provider
POST /ai/suggest-reply Logged in Generate an AI-suggested reply for a Q&A post

Web Push (web-push extension)

Method Route Auth Description
POST /push/subscribe Logged in Register a browser push subscription
DELETE /push/subscribe Logged in Remove a push subscription
GET /push/vapid-key Logged in Get the public VAPID key needed to subscribe
GET /push/service-worker.js Public Serves the push service-worker script

SEO Pro (seo-pro extension)

Method Route Auth Description
GET /spaces/{id}/seo Space Admin Get SEO metadata for a space (title, description, OG image)
POST /spaces/{id}/seo Space Admin Save SEO metadata for a space

Reply by Email (reply-by-email extension)

Method Route Auth Description
POST /reply-by-email/inbound Signature (webhook) Inbound webhook endpoint for processing email replies - validated by a signature inside the callback, not a user session

Advanced Moderation (advanced-moderation extension)

All advanced moderation endpoints require the manage_options capability.

Method Route Auth Description
GET /moderation/rules Admin List all auto-moderation rules
POST /moderation/rules Admin Create a new auto-moderation rule
PATCH /moderation/rules/{id} Admin Update an auto-moderation rule
DELETE /moderation/rules/{id} Admin Delete an auto-moderation rule
GET /moderation/rules/{id}/stats Admin Get trigger statistics for a specific rule

Analytics - additional endpoint (analytics extension)

Method Route Auth Description
GET /analytics/diff-report Admin (manage_options) Per-metric drift report comparing the direct-query path against the event-driven aggregate path; returns from_query, from_events, drift_pct, and within_tolerance for each metric

Community Announcements (site-announcements extension)

The management routes live under jetonomy-pro/v1 and require the manage_options or jetonomy_manage_spaces capability (administrators by default).

Method Route Description
GET /site-announcements List the currently pinned (announced) posts
POST /site-announcements/{id} Pin a post site-wide (capped at 5; returns 400 past the cap)
DELETE /site-announcements/{id} Remove a post from the announcements
// Pin post 395 to the whole community
await fetch( '/wp-json/jetonomy-pro/v1/site-announcements/395', {
    method:  'POST',
    headers: { 'X-WP-Nonce': nonce },
} );

This is distinct from the free space-level pin (POST /posts/{id}/pin), which only stickies a topic within its own space.

GET /announcements/active (added in 1.6.0)

Namespace: unlike the management routes above, this endpoint lives under jetonomy/v1.

A member-readable list of the currently active announcements, used by the mobile app's announcement banner. The read is public-aware: logged-out callers on a public community see the same site-wide pins the listing inject shows. Each item returns id, title, space_id, url (deep link to the post), and created_at.

{
  "data": [
    { "id": 395, "title": "Scheduled maintenance Sunday", "space_id": 12, "url": "https://example.com/community/s/news/t/maintenance/", "created_at": "2026-07-01 09:00:00" }
  ],
  "meta": { "total": 1 }
}

For the full Pro endpoint reference (methods, params, and permission callbacks per extension), see the Pro Endpoints section above.

Anonymous Posting (anonymous-posting extension)

Namespace: this route lives under jetonomy/v1, not jetonomy-pro/v1 - it mirrors the free jetonomy_author_can_reveal filter seam rather than registering a Pro-only namespace.

Method Route Auth Description
POST /anonymous/reveal Admin (manage_options) Reveal the real author of an anonymous post or reply

POST /anonymous/reveal - body

{
    object_type: 'post',   // or 'reply'
    object_id:   42,
}

Only site administrators can call this - space moderators cannot. Every successful reveal is written to the activity log (anonymous_author_revealed) with the real author ID, so reveals stay accountable. Returns 404 if the object is not actually anonymous, 403 if the caller lacks manage_options.

{ "success": true, "author": { "id": 17, "name": "Jane Doe" } }

File Attachments (attachments extension)

Method Route Auth Description
POST /attachments Logged in (jetonomy_create_posts, jetonomy_create_replies, or jetonomy_upload_media) Link an already-uploaded attachment to a post or reply
DELETE /attachments/{id} Owner / Moderator Detach an attachment from its post or reply
GET /attachments/{id}/download Public Download the file (forces Content-Disposition: attachment for non-image/PDF types)
GET /attachments/batch Admin (jetonomy_manage_settings) / Moderator (moderate_comments) Batch-read attachments for many posts or replies in one call

These routes are registered under jetonomy-pro/v1. {id} on DELETE and the download route is the attachment link ID, not the WordPress attachment ID.

POST /attachments - body

{
    object_type:   'post',   // or 'reply'
    object_id:     101,
    attachment_id: 4820,     // WP attachment ID from POST /jetonomy/v1/media
    sort:          0,        // optional
}

The file is re-validated against the allow-list on attach (defence in depth), and the per-object file cap from the Attachments settings is enforced server-side.

GET /attachments/batch - parameters

Parameter Type Required Description
object_type string Yes post or reply
object_ids string Yes Comma-separated list of IDs

Posts and replies also carry an attachments[] array directly on their normal GET/list responses (injected via jetonomy_rest_prepare_post / jetonomy_rest_prepare_reply), so most clients never need to call these routes directly except to attach or detach a file.


Error Responses

All errors follow the standard WP REST API format:

{
  "code":    "rest_forbidden",
  "message": "You are not allowed to do this.",
  "data":    { "status": 403 }
}

Common error codes:

Code HTTP Meaning
rest_forbidden 403 Missing capability or nonce
jetonomy_user_banned 403 The authenticated user is banned from the community (mutation routes)
jetonomy_pending_verification 403 The authenticated user has not confirmed their email yet (mutation routes)
rest_not_found 404 Resource does not exist
validation_error 422 Invalid or missing parameters
rate_limited 429 Too many requests from this user

Account-status enforcement (1.6.0)

Every write mutation rejects banned users (jetonomy_user_banned) and users who still owe email verification (jetonomy_pending_verification), both with HTTP 403. This runs inside the shared mutation permission callback (REST_Auth::auth_mutation()), so it applies uniformly to every mutation route. It fires even for requests authenticated with an Application Password: those credentials are minted outside the normal login flow, so enforcing the checks here closes a bypass where a banned or unverified account could otherwise still post through the API.


What's Next?

Hooks Reference

Jetonomy is built to be extended cleanly - every hook below is a real, supported extension point, generated straight from the source so this page never drifts from the code. Each row says what the hook is for and where it fires. New here? Start with the developer cookbook for step-by-step recipes (add a profile tab, customise a card, theme with tokens, extend the REST API), and the Coming from BuddyPress / BuddyBoss guide.

208 hooks, 141 with a description. filter = return a modified value; action = run a side effect. Args are listed where documented; the source file always has the full signature.

The 1.8.0 attachment filters (jetonomy_attachment_card, jetonomy_attachments_class, jetonomy_rest_attachment_data) are documented with usage examples on the dedicated Attachment Hooks page.

Posts

Hook What it does Args Source
jetonomy_after_create_post
action
- - includes/class-abilities.php
jetonomy_after_delete_post
action
Fires after a post is deleted. - includes/api/class-posts-controller.php
jetonomy_after_post_article
action
Named _article (not _content) to avoid collision with the existing jetonomy_after_post_content FILTER that injects HTML inside the… post_id templates/views/single-post.php
jetonomy_after_post_content
filter
- post_id, content templates/views/single-post.php
jetonomy_after_update_post
action
Fires after a post is updated. - includes/api/class-posts-controller.php
jetonomy_before_create_post
filter
Filter post data before creation. data includes/models/class-post.php
jetonomy_before_delete_post
filter
Filter whether a post deletion should proceed. post_id includes/models/class-post.php
jetonomy_new_post_fields
action
Fires inside the new-topic form, after the built-in fields. Anonymous Posting's "Post anonymously" checkbox and File Attachments' "Attach files" button render here. space templates/partials/compose-fields.php
jetonomy_new_post_submit_action
filter
- url templates/views/new-post.php
jetonomy_post_actions
action
- post_id templates/views/single-post.php
jetonomy_post_card_after_badges
action
Mirror the listing-card badge hook on the single-post header so Pro markers (notably the site-wide "Announcement" badge) show here too, not… $post, $space templates/views/single-post.php
jetonomy_post_created
action
idea_status, slug, etc.) for the listener to disambiguate by. - includes/models/class-post.php
jetonomy_post_deleted
action
- post_id includes/api/class-posts-controller.php
jetonomy_post_merged
action
- primary_id, secondary_id includes/models/class-post.php
jetonomy_post_meta_fields
action
Fires after the post body to display custom field values. post_id templates/views/single-post.php
jetonomy_post_publish_transition
action
Fired here for posts created directly as publish; Post::update() fires it for later transitions (pending→publish approval, publish→trash,… post_id, delta, created_at includes/models/class-post.php
jetonomy_post_response
filter
Alias filter matching the Pro custom-fields listener contract - lets extensions append per-post payload (custom field values, etc.) to the… response, post includes/api/class-posts-controller.php
jetonomy_post_updated
action
- post_id includes/api/class-posts-controller.php
jetonomy_posts_query_args
filter
Filter post query parameters before execution. - includes/models/class-post.php
jetonomy_scheduled_post_published
action
- post_id includes/models/class-post.php

Replies

Hook What it does Args Source
jetonomy_after_create_reply
action
- - includes/class-abilities.php
jetonomy_after_delete_reply
action
Fires after a reply is deleted. - includes/api/class-replies-controller.php
jetonomy_after_replies
action
Fires after the replies list renders. post_id templates/views/single-post.php
jetonomy_after_reply_content
filter
Filter appended after a reply's body content. File Attachments renders its attachment card strip here - the reply-side mirror of jetonomy_after_post_content. Since 1.7.0. content, reply templates/partials/reply-card.php
jetonomy_after_update_reply
action
Fires after a reply is updated with the full reply object plus context. - includes/api/class-replies-controller.php
jetonomy_before_create_reply
filter
Filter reply data before creation. data includes/models/class-reply.php
jetonomy_before_delete_reply
filter
Filter whether a reply deletion should proceed. reply_id includes/models/class-reply.php
jetonomy_before_replies
action
Fires before the replies list renders (above both empty state and populated list). post_id templates/views/single-post.php
jetonomy_between_replies
action
Fires after each top-level reply in the replies list. post_id, reply templates/views/single-post.php
jetonomy_replies_query_args
filter
Filter reply query parameters before execution. - includes/models/class-reply.php
jetonomy_reply_accepted
action
- reply_id includes/api/class-replies-controller.php
jetonomy_reply_actions
action
- reply_id templates/partials/reply-card.php
jetonomy_reply_created
action
content, etc.). - includes/models/class-reply.php
jetonomy_reply_deleted
action
- reply_id includes/api/class-replies-controller.php
jetonomy_reply_publish_transition
action
Mirrors jetonomy_post_publish_transition for the reply path - fired here for replies created directly as publish; Reply::update() fires… reply_id, delta, created_at includes/models/class-reply.php
jetonomy_reply_split
action
- new_post_id, reply_ids includes/models/class-reply.php
jetonomy_reply_unaccepted
action
- - includes/api/class-replies-controller.php
jetonomy_reply_updated
action
- reply_id includes/api/class-replies-controller.php

Voting

Hook What it does Args Source
jetonomy_after_vote
action
- user_id, object_type, object_id, vote_value includes/class-abilities.php
jetonomy_allow_self_upvote
filter
- allowed, user_id, type, id includes/api/class-votes-controller.php
jetonomy_before_vote
filter
Filter whether a vote should proceed. user_id, object_type, object_id, vote_value includes/models/class-vote.php
jetonomy_pro_poll_unvoted
action ·Pro
Fires after a user's votes are removed from a poll. $poll_id, $user_id includes/extensions/polls/class-extension.php
jetonomy_pro_poll_voted
action ·Pro
Fires after a vote is cast or changed on a poll. $poll_id, $user_id, $option_ids includes/extensions/polls/class-extension.php
jetonomy_vote_cast
action
Lets gamification reward the voter directly (the receiver is already covered by reputation). - includes/models/class-vote.php
jetonomy_vote_retracted
action
Fires after a voter retracts their existing vote. - includes/models/class-vote.php

Spaces & Membership

Hook What it does Args Source
jetonomy_after_create_space
action
Fires after a space is created. space_id, request includes/api/class-spaces-controller.php
jetonomy_after_update_space
action
Fires after a space is updated. space, context{space_id,user_id,request} includes/api/class-spaces-controller.php
jetonomy_before_join_space
filter
Filter whether a user should be allowed to join a space. user_id, space_id includes/models/class-space-member.php
jetonomy_import_space_visibility
filter
- access, source, forum includes/import/class-asgaros-importer.php
jetonomy_join_request_approved
action
- - includes/models/class-join-request.php
jetonomy_join_request_created
action
- request_id includes/cli/journeys/class-member-journey.php
jetonomy_join_request_denied
action
- - includes/models/class-join-request.php
jetonomy_max_space_pins
filter
- - includes/api/class-posts-controller.php
jetonomy_membership_activated
action
- user_id, level_id, adapter includes/adapters/class-member-press-adapter.php
jetonomy_membership_deactivated
action
- user_id, level_id, adapter includes/adapters/class-member-press-adapter.php
jetonomy_new_space_fields
action
Fires inside the create-space form, after the built-in fields. - templates/views/new-space.php
jetonomy_post_list_results_for_space
filter
Lets Pro extensions (site-announcements / super-sticky) inject cross-space pinned posts at the top of every space's view without each space… $results, $space_id, $user_id, $is_privileged, $sort, $offset includes/models/class-post.php
jetonomy_pro_ai_member_hourly_limit
filter ·Pro
Filter the per-user hourly AI request limit. - includes/extensions/ai/class-extension.php
jetonomy_pro_ai_member_min_trust
filter ·Pro
Filter the minimum trust level for member-facing AI. - includes/extensions/ai/class-extension.php
jetonomy_pro_ai_suggestion_space_types
filter ·Pro
- - includes/extensions/ai/class-suggester.php
jetonomy_show_sidebar_top_members
filter
- - templates/partials/sidebar.php
jetonomy_sidebar_after_top_members
action
Insert a custom widget or ad after the Top Members section. - templates/partials/sidebar.php
jetonomy_sidebar_before_top_members
action
Insert a custom widget or ad before the Top Members section. - templates/partials/sidebar.php
jetonomy_space_display_fields
action
Fires in the space header to display custom field values (context = space). space templates/views/space.php
jetonomy_space_edit_fields
action
Fires inside the edit-space form, after the built-in fields. space templates/views/space-edit.php
jetonomy_space_feed_posts
filter
Filters the posts included in a space RSS feed. posts, space includes/class-feed.php
jetonomy_space_listing_visibility_sql
filter
Filter the space-listing visibility SQL predicate. result, user_id, alias includes/models/class-space.php
jetonomy_space_member_joined
action
Alias of jetonomy_user_joined_space without the role arg - matches the Pro webhooks listener contract. - includes/models/class-space-member.php
jetonomy_space_member_left
action
Alias matching the Pro webhooks listener contract. - includes/models/class-space-member.php
jetonomy_space_members_per_page
filter
- - templates/views/space-members.php
jetonomy_space_pending_requests_shown
filter
- - templates/views/space-members.php
jetonomy_space_role_permissions
filter
Filter the permissions granted to a space role. permissions, role includes/permissions/class-permission-engine.php
jetonomy_space_tabs
filter
Add, remove, reorder, or relabel the tabs on a space page. $tabs, $space, $show_members templates/views/space.php
jetonomy_spaces_query_args
filter
Filter space query parameters before execution. - includes/models/class-space.php
jetonomy_use_frontend_space_edit
filter
Default true since G5 shipped in 1.4.0. use_frontend includes/functions.php
jetonomy_user_joined_space
action
- user_id, space_id includes/models/class-space-member.php
jetonomy_user_left_space
action
Fires when a user is removed from a space. - includes/models/class-space-member.php

Profiles

Hook What it does Args Source
jetonomy_profile_after_stats
action
Fires after the user stats bar on the profile page. user_id templates/views/user-profile.php
jetonomy_profile_display_fields
action
Fires after the stats bar to display custom profile field values. user_id templates/views/user-profile.php
jetonomy_profile_edit_fields
action
Fires after the standard profile edit fields, before submit. user_id templates/views/edit-profile.php
jetonomy_profile_response
filter
Filter the user profile REST response. response, user includes/api/class-users-controller.php
jetonomy_profile_tabs
filter
Add, remove, reorder, or relabel the tabs shown under a member's profile header. $tabs, $user, $is_own templates/views/user-profile.php
jetonomy_profile_url
filter
Allows third-party plugins (BuddyPress, BuddyBoss, Ultimate Member) to override where user profile links point to. url, user_id includes/functions.php

Roadmap

Hook What it does Args Source
jetonomy_idea_status_changed
action
Re-fires from the REST controller are tolerated: the controller snapshots $previous_status before this call, so a duplicate fire would… - includes/models/class-post.php

Moderation

Hook What it does Args Source
jetonomy_after_create_flag
action
- - includes/api/class-moderation-controller.php
jetonomy_after_resolve_flag
action
- - includes/moderation/class-moderation-service.php
jetonomy_content_moderated
action
- action, type, id, moderator_id includes/moderation/class-moderation-service.php
jetonomy_flag_created
action
- flag_id includes/api/class-posts-controller.php
jetonomy_flag_resolved
action
- flag_id, action includes/moderation/class-moderation-service.php
jetonomy_moderation_flag_resolution
filter
Filter the status applied to an object's pending flags when it is moderated directly (outside the flag-resolution flow). flag_status, action, type, id, user_id includes/moderation/class-moderation-service.php
jetonomy_moderation_per_page
filter
- - templates/views/moderation.php

Trust & Reputation

Hook What it does Args Source
jetonomy_pro_ai_review_exempt_trust_level
filter ·Pro
Filter the trust level at which members skip AI review. - includes/extensions/ai/class-batch-reviewer.php
jetonomy_pro_badge_earned
action ·Pro
Fires when a user earns a badge. $user_id, $badge_id, $badge includes/extensions/custom-badges/class-extension.php
jetonomy_pro_badge_revoked
action ·Pro
Fires when a badge is revoked from a user ($removed is the removed row). $user_id, $badge_id, $removed includes/extensions/custom-badges/class-extension.php
jetonomy_reputation_changed
action
of {@see award_custom()}. user_id, points, reason includes/trust/class-reputation.php
jetonomy_reputation_points_for
filter
Runs after the admin override + hardcoded fallback resolve, so filter listeners see the final number that would apply. $points, $action includes/trust/class-reputation.php
jetonomy_reputation_points_map
filter
Use this to add new action keys or wholesale-replace the scoring table (e.g. - includes/trust/class-reputation.php
jetonomy_reputation_pre_change
filter
Use this to scale deltas during campaigns ("double points weekend"), veto for sandboxed users (return 0), or redirect rep to an external… - includes/trust/class-reputation.php
jetonomy_trust_level_changed
action
- user_id, old_level, new_level includes/class-cli.php
jetonomy_trust_level_pre_change
filter
- - includes/class-cli.php
jetonomy_trusted_proxies
filter
Trusted reverse-proxy / CDN addresses (exact REMOTE_ADDR match). proxies includes/functions.php

Notifications

Hook What it does Args Source
jetonomy_notification_created
action
- notification_id, user_id, type, object_type, object_id, message, link includes/class-mentions.php
jetonomy_pro_message_notified
action ·Pro
Fires after message notifications are dispatched. $conversation_id, $sender_id, $preview includes/extensions/private-messaging/class-extension.php

Email

Hook What it does Args Source
jetonomy_create_reply_from_email
action ·Pro
Fire action for core Jetonomy to create the reply. $post_id, $user_id, $content, 'reply_by_email' includes/extensions/reply-by-email/class-extension.php
jetonomy_disposable_email_domains
filter
Filter the list of disposable email domains rejected on register. domains includes/api/class-auth-controller.php
jetonomy_email_accent_color
filter
Filter the accent color used in the email header accent-bar and CTA. color includes/notifications/class-notifier.php
jetonomy_email_body
filter
Filter the email body/intro text. body, template includes/notifications/class-notifier.php
jetonomy_email_headers
filter
Filter the headers before sending. headers includes/notifications/class-notifier.php
jetonomy_email_html
filter
Final filter on the rendered HTML. html, template includes/notifications/class-notifier.php
jetonomy_email_logo_url
filter
Filter the logo URL shown in the email header. url includes/notifications/class-notifier.php
jetonomy_email_subject
filter
Filter the email subject before sending. subject, template includes/notifications/class-notifier.php
jetonomy_email_template_context
filter
Filter the full context passed into email templates. context, template includes/notifications/class-notifier.php
jetonomy_email_template_path
filter
Filter the resolved email template path. path, template includes/notifications/class-notifier.php
jetonomy_email_verified
action
- user_id includes/api/class-auth-controller.php
jetonomy_notification_email_headers
filter
- headers includes/adapters/class-wp-mail-adapter.php

Templates & Layout

Hook What it does Args Source
jetonomy_after_content
action
Fires after the main content container closes, before the app wrapper closes. type, object_id includes/class-template-loader.php
jetonomy_before_content
action
Fires inside the Jetonomy app wrapper, before the header partial and content container. type, object_id includes/class-template-loader.php
jetonomy_check_content
filter
Check content against moderation rules before insertion. content, type includes/api/class-posts-controller.php
jetonomy_header_logo
filter
Filter the header logo URL used by Jetonomy-rendered surfaces. url includes/functions.php
jetonomy_header_nav_items
action
- user_id templates/partials/header.php
jetonomy_member_card_after
action
Append a per-member badge, link, or action here. $member, $space templates/views/space-members.php
jetonomy_show_sidebar
filter
Allow bridge plugins to suppress Jetonomy's sidebar (e.g. show templates/partials/sidebar.php
jetonomy_show_sidebar_about
filter
- - templates/partials/sidebar.php
jetonomy_show_sidebar_managed_by
filter
- - templates/partials/sidebar.php
jetonomy_show_sidebar_popular_tags
filter
- - templates/partials/sidebar.php
jetonomy_show_sidebar_trending
filter
- - templates/partials/sidebar.php
jetonomy_sidebar_about_after_meta
action
Fires inside the sidebar About card, after the meta tags. space_id templates/partials/sidebar.php
jetonomy_sidebar_after
action
Fires at the bottom of the Jetonomy sidebar, after all widgets render. - templates/partials/sidebar.php
jetonomy_sidebar_after_about
action
Fires in the sidebar immediately after the "About" space card closes. - templates/partials/sidebar.php
jetonomy_sidebar_after_managed_by
action
Insert a custom widget or ad after the Managed-by card. - templates/partials/sidebar.php
jetonomy_sidebar_after_popular_tags
action
Insert a custom widget or ad after the Popular Tags section. - templates/partials/sidebar.php
jetonomy_sidebar_after_trending
action
Insert a custom widget or ad after the Trending section. - templates/partials/sidebar.php
jetonomy_sidebar_auth_card
filter
Allow integrations to suppress the auto-rendered auth card. html includes/class-blocks.php
jetonomy_sidebar_before
action
Fires at the top of the Jetonomy sidebar, before any widgets render. - templates/partials/sidebar.php
jetonomy_sidebar_before_about
action
Insert a custom widget or ad before the About card. - templates/partials/sidebar.php
jetonomy_sidebar_before_managed_by
action
Insert a custom widget or ad before the Managed-by card. - templates/partials/sidebar.php
jetonomy_sidebar_before_popular_tags
action
Insert a custom widget or ad before the Popular Tags section. - templates/partials/sidebar.php
jetonomy_sidebar_before_trending
action
Insert a custom widget or ad before the Trending section. - templates/partials/sidebar.php
jetonomy_space_card_after
action
Fires after each space card. Append a per-space badge, link, or action here. $space templates/views/category.php, templates/views/home.php
jetonomy_template_map
filter
Values may be relative (resolved against plugin_dir/theme_dir) or absolute paths (starting with /). map includes/class-template-loader.php

Theme & CSS

Hook What it does Args Source
jetonomy_dynamic_css
filter
The string already contains the container width, palette tokens, font-inherit rules, the host-theme colour-adoption chain, density rules… $css, $settings includes/class-template-loader.php

SEO

Hook What it does Args Source
jetonomy_seo_meta
filter
@type string $title OG/Twitter title. - includes/class-template-loader.php

Media & Uploads

Hook What it does Args Source
jetonomy_upload_allowed_types
filter
The extension-to-MIME allow-list checked before POST /media accepts a file (extension AND sniffed MIME must both resolve to an allowed type; SVG is always excluded). Free defaults to images only. File Attachments widens this to add PDF and Office types. types includes/api/class-media-controller.php
jetonomy_upload_max_size
filter
The maximum accepted upload size in bytes, checked before POST /media accepts a file. Defaults to the smaller of the server's max upload size and 10 MB. max_bytes includes/api/class-media-controller.php

Query filters

Hook What it does Args Source
jetonomy_search_query_args
filter
Filters the search query args before the query is built. args includes/api/class-search-controller.php
jetonomy_users_query_args
filter
Filter user/leaderboard query parameters before execution. - includes/api/class-leaderboards-controller.php

Admin

Hook What it does Args Source
jetonomy_admin_dashboard_after_stats
action
Fires after the dashboard stat cards. - includes/admin/views/dashboard.php
jetonomy_admin_dashboard_widgets
action
Fires to render additional dashboard widgets. - includes/admin/views/dashboard.php
jetonomy_admin_footer_text
filter
Filter the admin footer text shown on Jetonomy admin pages. text includes/admin/class-admin.php
jetonomy_admin_license_tab_content
action
- - includes/admin/views/settings.php
jetonomy_admin_menu_icon
filter
- icon includes/admin/class-admin.php
jetonomy_admin_menu_label
filter
- label includes/admin/class-admin.php
jetonomy_admin_moderation_tab_content
action
Fires to render additional moderation tab content. active_tab includes/admin/views/moderation.php
jetonomy_admin_moderation_tabs
action
Fires to render additional moderation tabs. - includes/admin/views/moderation.php
jetonomy_admin_render_extensions
action
Fires to render the Extensions page content. - includes/admin/class-admin.php
jetonomy_admin_settings_tab_content
action
- tab, group includes/admin/views/settings.php
jetonomy_admin_settings_tabs
action
- - includes/admin/views/settings.php
jetonomy_admin_space_edit_tab_content
action
Fires to render additional space edit tab content. space_id, tab includes/admin/views/space-edit.php
jetonomy_admin_space_edit_tabs
action
Fires to render additional space edit tabs. space_id includes/admin/views/space-edit.php

REST

Hook What it does Args Source
jetonomy_app_config
filter
Filter the mobile app config payload (branding + feature flags) served by GET /app/config. Pro consumes it to set app_enabled and inject white-label branding. Since 1.6.0. data, request includes/api/class-app-config-controller.php
jetonomy_rest_prepare_notification
filter
Filter the REST response data for a single notification. response, notification includes/api/class-notifications-controller.php
jetonomy_rest_prepare_post
filter
Filter the REST response data for a single post. response, post includes/api/class-posts-controller.php
jetonomy_rest_prepare_reply
filter
Filter the REST response data for a single reply. response, reply includes/api/class-replies-controller.php
jetonomy_rest_prepare_space
filter
Filter the REST response data for a single space. response, space includes/api/class-spaces-controller.php
jetonomy_rest_prepare_user
filter
Filter the REST response data for a single user. response, user includes/api/class-users-controller.php

Integrations

Hook What it does Args Source
jetonomy_automator_space_options_limit
filter ·Pro
Filter the number of spaces offered in the Uncanny Automator dropdowns (default 500). - includes/integrations/class-automator-integration.php
jetonomy_client_ip
filter
Final resolved client IP. ip, remote_addr includes/functions.php
jetonomy_companions
filter
Filter the Wbcom stack companion catalog. - includes/integrations/class-companion-registry.php

Other

Hook What it does Args Source
jetonomy_author_can_reveal
filter
Whether the current viewer may see the real author of an anonymous post/reply. Free always returns false - anonymous stays masked for everyone. Pro's Anonymous Posting extension grants an explicit-reveal context to site admins only. Since 1.7.0. can_reveal, object, viewer_id includes/class-author.php
jetonomy_composer_toolbar
action
Fires inside the composer toolbar, after the built-in formatting buttons. - templates/partials/composer.php
jetonomy_cron_batch_size
filter
- - includes/class-cron.php
jetonomy_erase_batch_size
filter ·Pro
Filter the batch size used by the GDPR personal-data eraser (default 1000). - includes/class-privacy.php
jetonomy_footer_text
filter
Filter the footer text used by Jetonomy-rendered surfaces. text includes/functions.php
jetonomy_home_welcome_heading
filter
- - templates/views/home.php
jetonomy_home_welcome_subheading
filter
- - templates/views/home.php
jetonomy_import_wpforo_guest_group
filter
- guest_group includes/import/class-wpforo-importer.php
jetonomy_importers
filter
- importers includes/import/class-import-manager.php
jetonomy_leaderboard_items
filter
Lets host plugins enrich each row with cross-engine totals (badge count, level name, alternate currency) without a second REST round-trip. - includes/api/class-leaderboards-controller.php
jetonomy_learnomy_max_levels
filter ·Pro
- - includes/adapters/class-learnomy-adapter.php
jetonomy_link_preview_cache_ttl
filter
- ttl includes/services/links/class-preview-service.php
jetonomy_link_preview_data
filter
- data, url includes/services/links/class-preview-service.php
jetonomy_link_preview_providers
filter
- providers includes/services/links/class-preview-service.php
jetonomy_link_preview_user_agent
filter
- ua includes/services/links/class-html-fetcher.php
jetonomy_oembed_response
filter
Filter the oEmbed response payload for a Jetonomy thread. response, post includes/api/class-oembed-controller.php
jetonomy_orphan_purge_batch_seconds
filter
Filter the wall-clock budget (default 15) one wp jetonomy privacy purge-orphans slice may run before stopping cleanly at a user boundary. seconds includes/class-privacy-backfill.php
jetonomy_privacy_orphan_columns
filter
Filter the (table, column, where) discovery targets scanned for orphaned user ids; Pro adds its own jt_pro_* columns here rather than shipping a parallel sweeper. columns includes/class-privacy-backfill.php
jetonomy_purge_orphan_user
action
Purge every trace of a user id that no longer exists. Fired per orphaned id by wp jetonomy privacy purge-orphans; free's and Pro's Privacy::on_user_delete() both listen, so it reuses the real delete path. user_id includes/class-privacy-backfill.php
jetonomy_search_filters
action
- - templates/views/search.php
jetonomy_show_community_nav
filter
Filter whether to show the Jetonomy community navigation bar. show templates/partials/header.php
jetonomy_theme_dark_tokens
filter
Return an empty array to disable the dark override. tokens includes/integrations/class-theme-integration.php
jetonomy_theme_light_tokens
filter
Return an empty array to disable the light override. tokens includes/integrations/class-theme-integration.php
jetonomy_user_pending_verification
action
- user_id includes/api/class-auth-controller.php
jetonomy_user_registered
action
- user_id includes/api/class-auth-controller.php
jetonomy_verification_reminder_sent
action
Fires after a verification reminder email is dispatched. user_id, user includes/notifications/class-verification-reminder.php

Pro

Pro-only hooks (marked Pro above and listed below) fire from the Jetonomy Pro extensions, and only when the corresponding extension is active. Each hook's source extension is noted in the Source column.

Hook What it does Args Source
jetonomy_pro_ai_all_providers_failed
action ·Pro
- $feature, $exception includes/extensions/ai/class-spam-detector.php
jetonomy_pro_ai_review_batch_cap
filter ·Pro
Filter the maximum number of items inspected per content type (posts and replies are each capped at this) per sweep. - includes/extensions/ai/class-batch-reviewer.php
jetonomy_pro_ai_review_chunk_size
filter ·Pro
Filter how many items are classified per model call. - includes/extensions/ai/class-batch-reviewer.php
jetonomy_pro_ai_review_interval
filter ·Pro
Filter the batch review interval in seconds. - includes/extensions/ai/class-batch-reviewer.php
jetonomy_pro_ai_suggestions_only_unanswered
filter ·Pro
- - includes/extensions/ai/class-suggester.php
jetonomy_pro_ai_summary_heading
filter ·Pro
- - includes/extensions/ai/class-summarizer.php
jetonomy_pro_analytics_dual_path_verify
filter ·Pro
Dual-path verification is an internal diagnostics tool (direct query vs. - includes/extensions/analytics/views/dashboard.php
jetonomy_pro_conversation_archived
action ·Pro
Fires after a conversation is archived or unarchived. - includes/extensions/private-messaging/class-extension.php
jetonomy_pro_conversation_blocked
action ·Pro
Fires after a user blocks or unblocks the other party in a direct conversation. - includes/extensions/private-messaging/class-extension.php
jetonomy_pro_conversation_created
action ·Pro
Fires after a new conversation is created. $conversation_id, $user_id, $participants includes/extensions/private-messaging/class-extension.php
jetonomy_pro_conversation_left
action ·Pro
Fires after a user leaves a group conversation. - includes/extensions/private-messaging/class-extension.php
jetonomy_pro_conversation_purged
action ·Pro
Fires after a site admin purges a conversation from wp-admin. conversation_id, admin_id includes/extensions/private-messaging/class-admin-page.php
jetonomy_pro_dm_received
action ·Pro
Counterpart to jetonomy_pro_message_sent. - includes/extensions/private-messaging/class-extension.php
jetonomy_pro_field_created
action ·Pro
Fires after a custom field is created. $field_id, $context includes/extensions/custom-fields/class-extension.php
jetonomy_pro_first_reaction
action ·Pro
Fires the first time an object receives any reaction. $object_type, $object_id, $user_id includes/extensions/reactions/class-extension.php
jetonomy_pro_message_sent
action ·Pro
Fires after a message is sent. $message_id, $conversation_id, $sender_id includes/extensions/private-messaging/class-extension.php
jetonomy_pro_poll_created
action ·Pro
Fires after a poll is created. $poll_id, $post_id, $user_id includes/extensions/polls/class-extension.php
jetonomy_pro_reaction_icon_renderer
filter ·Pro
Sites that want the SVG look can opt in via the filter below. $renderer, $slug, $size includes/extensions/reactions/class-extension.php
jetonomy_pro_reaction_toggled
action ·Pro
Fires after a reaction is toggled. $object_type, $object_id, $emoji, $user_id, $action includes/extensions/reactions/class-extension.php

Jetonomy's template system is designed to be overridden without touching plugin files. Place your custom templates in your theme and they will be loaded automatically - no hooks, no filters required for simple overrides.


How It Works

When Jetonomy loads a template, Template_Loader checks your active theme directory first. If a matching file exists there, it loads that file instead of the plugin's copy. This means your overrides survive plugin updates.

Resolution order:

  1. {your-theme}/jetonomy/{relative-path} - checked first
  2. {jetonomy-plugin}/templates/{relative-path} - fallback default

The {relative-path} is always the same path the plugin uses internally - for example, views/home.php or partials/reply-card.php.


Directory Structure

Create the following folder structure in your active theme:

your-theme/
└── jetonomy/
    ├── views/
    │   ├── home.php
    │   ├── category.php
    │   ├── space.php
    │   ├── space-members.php
    │   ├── space-roadmap.php
    │   ├── single-post.php
    │   ├── new-post.php
    │   ├── user-profile.php
    │   ├── edit-profile.php
    │   ├── leaderboard.php
    │   ├── notifications.php
    │   ├── moderation.php
    │   ├── search.php
    │   ├── tag.php
    │   └── invite.php
    └── partials/
        ├── avatar.php
        ├── breadcrumb.php
        ├── composer.php
        ├── header.php
        ├── pagination.php
        ├── post-card.php
        ├── reply-card.php
        └── sidebar.php

You do not need to copy all of these. Only create the files you want to customize - any file not present in your theme directory is loaded from the plugin.


Available Templates

Views (full-page templates)

File Route URL Pattern
views/home.php home /community/
views/category.php category /community/category/{slug}/
views/space.php space /community/s/{slug}/
views/space-members.php space-members /community/s/{slug}/members/
views/space-roadmap.php space-roadmap /community/s/{slug}/roadmap/
views/space-edit.php edit-space /community/s/{slug}/edit/
views/space-moderation.php space-moderation /community/s/{slug}/mod/
views/single-post.php post /community/s/{slug}/t/{slug}/
views/new-post.php new-post /community/s/{slug}/new/
views/new-space.php new-space /community/new-space/
views/my-spaces.php my-spaces /community/my-spaces/
views/drafts.php drafts /community/drafts/
views/bookmarks.php bookmarks /community/bookmarks/
views/user-profile.php profile /community/u/{login}/
views/edit-profile.php edit-profile /community/u/{login}/edit/
views/leaderboard.php leaderboard /community/leaderboard/
views/notifications.php notifications /community/notifications/
views/moderation.php moderation /community/mod/
views/search.php search /community/search/
views/tag.php tag /community/tag/{slug}/
views/invite.php invite /community/invite/{code}/

Partials (reusable template fragments)

File Rendered via
partials/header.php Loaded at the top of every community page
partials/sidebar.php Loaded in space and home views
partials/breadcrumb.php Loaded in space, category, and post views
partials/post-card.php Iterated over in space and home views
partials/reply-card.php Iterated over in single-post view
partials/pagination.php Loaded at the bottom of listing pages
partials/avatar.php Used inside post-card, reply-card, and profile views
partials/composer.php Rich-text composer used in new-post and reply forms

Step-by-Step: Overriding the Post Card

This example adds a "Sponsored" badge to posts from a specific space.

Step 1: Copy the original template

Copy wp-content/plugins/jetonomy/templates/partials/post-card.php to:

your-theme/jetonomy/partials/post-card.php

Step 2: Modify your copy

Open your-theme/jetonomy/partials/post-card.php and add your customization. The variable $post (a \Jetonomy\Models\Post object) is available inside the partial.

<?php
// your-theme/jetonomy/partials/post-card.php

// The $post variable is passed via Template_Loader::partial()
$is_sponsored = ( (int) $post->space_id === MY_SPONSORED_SPACE_ID );
?>

<div class="jt-row <?php echo $is_sponsored ? 'jt-row--sponsored' : ''; ?>">
    <?php if ( $is_sponsored ) : ?>
        <span class="my-sponsored-badge">Sponsored</span>
    <?php endif; ?>

    <?php
    // Continue with the original template output.
    // Tip: call Template_Loader::partial() for nested partials so they also
    // respect theme overrides.
    \Jetonomy\Template_Loader::partial( 'avatar', [ 'user_id' => $post->author_id ] );
    ?>

    <div class="jt-row__body">
        <?php // There is no single post-URL helper; build it from base_url + the space and post slugs (same as core templates). ?>
        <a href="<?php echo esc_url( \Jetonomy\base_url() . '/s/' . ( $space->slug ?? '' ) . '/t/' . $post->slug . '/' ); ?>">
            <?php echo esc_html( $post->title ); ?>
        </a>
    </div>
</div>

Step 3: Add styles

Add CSS to your theme for the new .jt-row--sponsored class. Reference Jetonomy's CSS tokens so your styles adapt to dark mode automatically:

/* your-theme/style.css or an enqueued stylesheet */

.jt-row--sponsored {
    border-left: 3px solid var(--jt-accent);
}

.my-sponsored-badge {
    display: inline-block;
    font-size: 0.75rem;
    color: var(--jt-text-secondary);
    background: var(--jt-accent-light);
    border-radius: var(--jt-radius-sm);
    padding: 2px 6px;
}

Calling Partials from Templates

Inside any view or partial, use Template_Loader::partial() instead of a raw include. This ensures the theme-override check runs for nested partials too:

// Inside your custom view or partial:
\Jetonomy\Template_Loader::partial( 'pagination', [
    'total'    => $total,
    'per_page' => 20,
    'page'     => $current_page,
] );

The second argument is passed as local variables to the partial (via extract()).


Available Variables in Templates

Templates receive route data via $data (array with route and slug keys). Most views also query the database directly at the top of the file. When you copy a template, review the top of the plugin's original file to understand which variables are set before the HTML output begins.

Common objects available in plugin templates:

Variable Type Available in
$data array All views
$space \Jetonomy\Models\Space space, space-members, new-post
$post \Jetonomy\Models\Post single-post, post-card partial
$reply \Jetonomy\Models\Reply reply-card partial
$user WP_User user-profile, edit-profile
$posts array home, space, search, tag
$settings array All views (from get_option('jetonomy_settings'))

The jetonomy_template_map Filter

For cases where you need to register a completely new route - or override the template for an existing route with a file stored outside the theme - use the jetonomy_template_map filter.

add_filter( 'jetonomy_template_map', function( array $map ): array {
    // Override an existing route with an absolute path.
    $map['leaderboard'] = get_stylesheet_directory() . '/jetonomy/views/leaderboard.php';

    // Register a brand-new route (accessible at /community/events/).
    $map['events'] = MY_PLUGIN_DIR . 'templates/community-events.php';

    return $map;
} );

Important: If you provide an absolute path (starting with /), the theme-override check is bypassed - the file you point to is loaded directly. This is the correct approach for Pro extensions and companion plugins that ship their own templates.

The Jetonomy Router must know about new routes before they can receive traffic. Register rewrite rules alongside the template map:

// Register a custom rewrite rule for /community/events/.
add_action( 'init', function() {
    $settings  = get_option( 'jetonomy_settings', [] );
    $base_slug = $settings['base_slug'] ?? 'community';

    add_rewrite_rule(
        '^' . preg_quote( $base_slug, '^' ) . '/events/?$',
        'index.php?jetonomy_route=events',
        'top'
    );
} );

// Teach WordPress to recognize the query var.
add_filter( 'query_vars', function( array $vars ): array {
    $vars[] = 'jetonomy_route';
    return $vars;
} );

After adding rewrite rules, flush permalinks once: go to Settings → Permalinks and click Save Changes, or run:

wp --path="/path/to/wordpress" rewrite flush

Child Theme Compatibility

If you are using a child theme, place overrides in the child theme directory - get_stylesheet_directory() resolves to the child theme path when a child theme is active. The plugin does not check the parent theme separately, so all overrides must live in the active (child) theme.


Pro Template Overrides

Jetonomy Pro registers its own templates for Private Messaging via the jetonomy_template_map filter using absolute paths. You can override Pro templates in your theme using the same directory structure:

your-theme/
└── jetonomy/
    └── views/
        ├── messages.php      # /community/messages/
        └── conversation.php  # /community/messages/{id}/

Place overrides here and they will be detected automatically because Pro's Template_Loader::partial() calls still respect the theme directory check for non-absolute paths.


What's Next?

Jetonomy includes eight shortcodes, four classic widgets, and eight Gutenberg blocks so you can embed community content anywhere on your WordPress site - sidebars, pages, posts, or block-based layouts.

What You Will Learn

  • How to use the eight built-in shortcodes and their attributes
  • How to add the four classic widgets to sidebar areas
  • How to insert the eight Gutenberg blocks in the block editor
  • How shortcodes and blocks share the same rendering logic
  • Which surfaces are block-only and which are shortcode-only

At a Glance

Type Slug Purpose Since
Shortcode [jetonomy_recent_posts] Recent posts feed 1.0
Shortcode [jetonomy_trending_posts] Hot-scored trending posts 1.3.0
Shortcode [jetonomy_spaces] Space directory grid 1.0
Shortcode [jetonomy_leaderboard] Top members by reputation 1.0
Shortcode [jetonomy_user_profile] Single user profile card 1.2
Shortcode [jetonomy_space_members] Members of one space 1.2
Shortcode [jetonomy_compose_topic] New in 1.3.7 - inline topic composer (fixed space or member picker) 1.3.7
Shortcode [jetonomy_widget] Embed any of the four classic widgets on a page or page-builder canvas 1.4.0
Block jetonomy/forum-feed Live post feed 1.3.0
Block jetonomy/trending Trending topics with time-decayed hot score 1.3.6
Block jetonomy/space-list Space grid 1.3.0
Block jetonomy/leaderboard Top members 1.3.0
Block jetonomy/navigation Permission-aware category + space tree 1.3.5
Block jetonomy/user-panel Logged-in sidebar profile card 1.3.5
Block jetonomy/login Inline login/register panel 1.3.5
Block jetonomy/compose-topic New in 1.3.7 - topic composer embeddable anywhere 1.3.7

Block-only vs shortcode-only

Most surfaces are available as both a block and a shortcode (Forum Feed, Trending, Space List, Leaderboard, Compose Topic). Three are available one way only:

  • Block-only (no shortcode twin): jetonomy/navigation, jetonomy/user-panel, jetonomy/login. To place these outside the block editor, add the block inside a reusable/synced pattern or a block-based template part.
  • Shortcode-only (no block twin): [jetonomy_user_profile], [jetonomy_space_members], [jetonomy_widget]. Use these in classic editors, page builders, and widget areas.

Shortcodes

All shortcodes are registered by Jetonomy\Shortcodes::register() and are available on any page or post.

Self-styling on any page (1.4.0+): every shortcode auto-enqueues assets/css/blocks.css at render time and ships its own --jtb-* token block, so a bare [jetonomy_recent_posts] paste on a regular WordPress page renders fully styled - no need to put it inside a Forum Feed block container. Works in classic editor, page builders (Elementor, Divi, Bricks, WPBakery), widget areas, and template parts.


[jetonomy_recent_posts]

Displays a list of the most recent published posts across your community or within a specific space.

Attributes

Attribute Type Default Description
count int 5 Number of posts to display
space_id int 0 Restrict to a space. 0 = all spaces
sort string latest latest or votes
[jetonomy_recent_posts count="5" space_id="3" sort="votes"]

Each post card shows the title, author name, space name, time ago, vote score, and reply count.


[jetonomy_trending_posts]

Displays a ranked list of "hot" posts using a time-decayed score of recent votes + replies.

Attributes

Attribute Type Default Description
count int 5 Number of posts to display
space_id int 0 Restrict to a space. 0 = all spaces
window int 7 Days of history used for the hot score
[jetonomy_trending_posts count="10" window="14"]

[jetonomy_spaces]

Displays a list of public, active spaces, ordered by post count.

Attributes

Attribute Type Default Description
count int 6 Number of spaces to display
category_id int 0 Filter by category. 0 = all categories
[jetonomy_spaces count="6" category_id="2"]

Each space card shows the title, a short description excerpt, and the post count.


[jetonomy_leaderboard]

Displays a ranked list of the top community members by reputation score.

Attributes

Attribute Type Default Description
count int 10 Number of members to display
[jetonomy_leaderboard count="10"]

[jetonomy_user_profile]

Displays a compact profile card for a specific user or the currently logged-in user.

Attributes

Attribute Type Default Description
user_id int 0 Target user. 0 = current logged-in user
[jetonomy_user_profile user_id="0"]

The card shows the display name, trust level badge, bio excerpt, reputation score, and post count.


[jetonomy_space_members]

Displays a list of members for a specific space, ordered by reputation.

Attributes

Attribute Type Default Description
space_id int - Required. ID of the space
count int 10 Number of members to display
[jetonomy_space_members space_id="5" count="20"]

[jetonomy_compose_topic] (new in 1.3.7)

Lets signed-in members start a new topic from any WordPress page, post, or page-builder canvas. Two modes: lock to one space, or show a picker of spaces the current user is a member of.

Attributes

Attribute Type Default Description
mode string picker picker (show a space select) or fixed (post to one space)
space_id int 0 Space ID to post into. Only used when mode="fixed". Invalid IDs degrade to picker at render time.
types CSV topic,question,idea Allowed post types for this embed
[jetonomy_compose_topic mode="picker"]
[jetonomy_compose_topic mode="fixed" space_id="5"]

Compose Topic shortcode rendered on a regular WordPress page

Behavior

  • Logged-out viewers see a "Sign in to start a new topic" CTA that redirects back to the current URL after login - no form exposure, no wasted scroll.
  • Picker mode queries Permission_Engine::can($uid, 'create_posts', $space_id) against every space the user is a member of. Only spaces where they actually have posting rights appear.
  • Fixed mode hides the picker entirely. If the hardcoded space_id is missing or the user cannot post in it, the embed silently degrades to picker mode rather than breaking.
  • Assets (blocks.css + the Interactivity API bundle) enqueue on-demand at render time so pages that don't use the shortcode carry no overhead. Works inside page builders that render shortcodes outside the_content (Elementor, Divi, Bricks, WPBakery).

Companion REST endpoint: GET /jetonomy/v1/spaces?postable_by_me=1 returns the user's postable spaces.

When the title is filled but the body is empty, an inline error banner appears above the title - no silent failures, no lost input:

Inline validation banner when the body is empty


Classic Widgets

Jetonomy registers four classic widgets for use in any theme widget area. Each widget is configured through the standard WordPress widget admin screen or the Customizer.

Recent Posts Widget

Displays recent forum posts in any sidebar or widget area.

Settings: Title, Count, Space (optional filter), Sort order

Leaderboard Widget

Displays top community contributors ranked by reputation.

Settings: Title, Count

Active Spaces Widget

Displays the most active spaces by post count.

Settings: Title, Count

User Stats Widget

Displays the currently logged-in user's stats: reputation, post count, reply count, and trust level.

Settings: Title (no other configuration - always reflects the current user)

[jetonomy_widget] shortcode (new in 1.4.0)

Each classic widget above can also be embedded directly into any page or page-builder canvas - without dropping into the Customizer or a sidebar - using the [jetonomy_widget] shortcode.

Attributes

Attribute Type Required Description
id string yes Widget id: jetonomy_recent_posts, jetonomy_leaderboard, jetonomy_active_spaces, or jetonomy_user_stats
title string no Override the widget's title (otherwise the widget's saved Customizer title or the default)
count int no For Recent Posts, Leaderboard, and Active Spaces
space_id int no For Recent Posts, optional space filter
sort string no For Recent Posts: latest or votes
[jetonomy_widget id="jetonomy_recent_posts" count="8" sort="latest"]
[jetonomy_widget id="jetonomy_leaderboard" count="10"]
[jetonomy_widget id="jetonomy_active_spaces" count="6"]
[jetonomy_widget id="jetonomy_user_stats" title="Your stats"]

Internally [jetonomy_widget] wraps WordPress core's the_widget() so the rendered markup matches what the same widget would output in a sidebar - same hooks, same CSS classes, same i18n. Useful for landing pages, footer columns, and page-builder canvases where the Customizer's sidebar widget area isn't available.


Gutenberg Blocks

Jetonomy registers eight server-side rendered blocks. Each block uses a render_callback that delegates to the matching shortcode (for most) or a dedicated render component (for Navigation, User Panel, and Login). Output is consistent wherever they appear.

All blocks live in the Widgets category of the block inserter and answer to the search term jet.

This is how the content blocks render on a published page - Forum Feed, Trending, Space List, and Leaderboard dropped onto a regular WordPress page. Because blocks and shortcodes share the same render path, the matching [jetonomy_*] shortcodes produce identical output:

Forum Feed, Trending, Spaces, and Leaderboard blocks rendered on a published WordPress page

Backend (editor) vs frontend (published) render (1.4.0+)

The two surfaces render differently - by design.

  • Backend (block editor) - each block paints a framed static preview card with a "JETONOMY" pill badge, the block title, and an attribute-aware hint that reflects the current settings (Filtered to space #3, 7 day window, All public spaces, etc.). No REST calls fire. Dropping a block onto a page is instant; switching between Visual and Code editors does not trigger a network roundtrip. The preview is a mock - what the editor shows is not the actual queried data.
  • Frontend (published page) - the block's PHP render_callback runs against the live database on every request. Output is the same markup the matching [jetonomy_*] shortcode produces, with the block's wp-block-jetonomy-* wrapper class applied. Output is permission-aware: private spaces, banned authors, and silenced posts are filtered exactly as they would be on a regular community page.

Reason for the split: a live-data preview in the editor would (a) hammer the REST API on every keystroke in the title field, (b) leak permission-gated content to anyone who can edit the post, and (c) require a working REST connection at edit time. The static-card pattern mirrors what core blocks like Latest Posts do at the editor level, with the exception that core's render path can hit useEntityRecords() cheaply because the data is already cached client-side.

Each block exposes its settings through Inspector controls in the right sidebar, sized at the WordPress 6.7+ default (40px) with no deprecated bottom margins. The controls map one-to-one to the block attributes documented per block below.

Block inserter visibility was tightened in 1.4.0 - every block now registers an editor-side script (assets/js/blocks-editor.js) so all eight blocks appear in the inserter at once. Pre-1.4.0 only Compose Topic was visible because it was the only block carrying its own editor_script.

In the block editor each block paints the same lightweight preview card - a JETONOMY badge, the block name, and a hint that reflects the current settings (All public spaces, 7 day window, Categories → spaces tree, permission-aware) - so you can lay out a page without firing a single REST call:

Jetonomy blocks shown as static preview cards in the Gutenberg block editor

jetonomy/forum-feed

Renders a live post feed from a selected space or all spaces.

Block Attributes

Attribute Type Default Description
count number 5 Posts to show
spaceId number 0 Space ID (0 = all spaces)
sort string latest latest or votes
showHeader boolean false Render a space header above the feed
title string '' Custom title when showHeader is on

jetonomy/trending (1.3.6+)

Renders a ranked list of hot topics using a time-decayed score of recent engagement. Same render_callback plumbing as [jetonomy_trending_posts].

Block Attributes

Attribute Type Default Description
count number 5 Posts to show
spaceId number 0 Restrict to a space (0 = all)
window number 7 Days of history for the hot score
showHeader boolean true Render a "Trending" header
title string '' Custom title

jetonomy/space-list

Renders a list of community spaces. Supports category filtering.

Block Attributes

Attribute Type Default Description
count number 6 Spaces to show
categoryId number 0 Filter by category (0 = all)

jetonomy/leaderboard

Renders a leaderboard of top community members by reputation.

Block Attributes

Attribute Type Default Description
count number 10 Members to show

jetonomy/navigation (1.3.5+)

Renders the Category → Space tree as permission-aware sidebar navigation. Designed for the community sidebar of any block theme or widget area.

Why this block exists

Most community themes render the space list with a hand-maintained nav menu. That list rots the moment you add a space, and it leaks private spaces to anonymous viewers. This block queries the live category/space tree on every render and honors Jetonomy's permission layer, so private spaces stay hidden from viewers who do not have access.

Block Attributes

Attribute Type Default Description
showCategoryHeadings boolean true Group spaces by parent category
collapsible boolean false Collapsible category headings
showPostCount boolean false Show topic count next to each space
hideEmptyCategories boolean true Hide categories that have no visible spaces
title string '' Optional wrapper title

Scales to sites with thousands of spaces - the rendered tree uses Jetonomy's cached category/space index, not a per-request DB scan.

This block is block-only - there is no shortcode twin - so the rendered output is shown here:

Jetonomy Navigation block rendered in a community sidebar as a permission-aware Category to Space tree


jetonomy/user-panel (1.3.5+)

Renders a compact profile card for logged-in viewers - avatar, display name, notifications count, quick links to Profile / Notifications / Messages / Edit Profile / Logout. Empty for logged-out viewers so the sidebar layout doesn't shift.

Jetonomy User Panel block showing a logged-in member's avatar, name, notifications count, and quick links

Block Attributes

Attribute Type Default Description
title string '' Optional wrapper title above the card

Auto-injects at the top of the community sidebar for logged-in viewers so admins don't need to add it by hand (disable with add_filter( 'jetonomy_sidebar_auth_card', '__return_false' )).


jetonomy/login (1.3.5+)

Renders an inline login and register panel for the community sidebar. Logged-out viewers see Login and Register tabs without leaving the page. Logged-in viewers get nothing rendered - no layout shift when state changes.

Jetonomy Login block showing the Login and Register tabs rendered inline on a community page

Block Attributes

Attribute Type Default Description
title string '' Header above the tabs
showRegister boolean true Show the Register tab alongside Login (honours users_can_register)

Security

  • Both forms are nonce-protected (wp_ajax_jetonomy_quick_login / wp_ajax_jetonomy_quick_register)
  • Failed login attempts are rate-limited via Jetonomy\Security\Rate_Limiter (5 attempts / 15 minutes per IP)
  • Registration respects whatever users_can_register is set to in your WP admin and any anti-spam adapter you have active (Akismet, AI spam detection)

jetonomy/compose-topic (new in 1.3.7)

Gutenberg equivalent of [jetonomy_compose_topic]. Drop it on any page, post, or template part and signed-in members can start a topic without leaving the page.

Block Attributes

Attribute Type Default Description
mode string picker picker or fixed
spaceId number 0 Space ID (fixed mode only)
types string topic,question,idea Allowed post types

Editor experience

Compose Topic block in the Gutenberg editor

  • The block editor shows a static preview (no live REST calls) - safe to drop into any page without hitting the server.
  • Inspector controls: Mode select (picker / fixed), Space ID (visible only when Mode is fixed), Allowed types (comma-separated).
  • Falls back to picker mode at render time if the fixed spaceId doesn't resolve to a space the viewer can post in - so themes/pages that were built before a space was deleted keep working instead of 500'ing.

Rendering

  • Server render delegates to [jetonomy_compose_topic], so the block + shortcode output are pixel-identical.
  • Styles come from assets/css/blocks.css - self-contained, inherits theme tokens through --wp--preset--* fallbacks so it looks correct outside Jetonomy templates.
  • Built-in mobile breakpoint at 640px - submit button spans the column width, actions stack vertically.

CSS Classes for Styling

All shortcode and block output uses the jt-shortcode CSS class prefix so you can style them in your theme without affecting core community pages:

Class Element
.jt-shortcode Wrapper on all shortcode output
.jt-shortcode-recent-posts Recent posts container
.jt-shortcode-post Individual post card
.jt-shortcode-post-title Post title link
.jt-shortcode-post-meta Author, space, and time line
.jt-shortcode-post-stats Vote and reply counts
.jt-shortcode-spaces Spaces container
.jt-shortcode-space Individual space card
.jt-shortcode-space-desc Space description excerpt
.jt-shortcode-space-stats Space post-count line
.jt-shortcode-trending-post Trending post row
.jt-shortcode-trending-rank Trending rank badge (1, 2, 3 …)
.jt-shortcode-trending-body Trending row body (title + meta + stats)
.jt-shortcode-leaderboard Leaderboard container
.jt-shortcode-rep Reputation pill in leaderboard / member rows
.jt-shortcode-profile-card User profile card
.jt-shortcode-profile-stats Reputation + post-count line on profile card
.jt-shortcode-members Members list container
.jt-shortcode-member Individual member row
.jt-shortcode-empty Empty state message
.jt-compose-topic-embed Compose-topic shortcode/block wrapper
.jt-compose-topic-embed.jt-compose-topic-login Logged-out sign-in CTA variant
.jt-compose-topic-field Label + input group
.jt-compose-topic-space Space picker <select>
.jt-compose-topic-title Title <input>
.jt-compose-topic-body Details <textarea>
.jt-compose-topic-submit Post topic button
.jt-compose-topic-error Inline error banner (shown via state.submitError)
.jt-compose-topic-posting-to "Posting in …" line in fixed mode

Building Companion Shortcodes or Blocks

If you are building a companion plugin that needs to query Jetonomy data, guard your code with a class existence check:

if ( ! defined( 'JETONOMY_VERSION' ) ) {
    return;
}

Use the model classes for server-side rendering or the REST API for client-side fetches. See the REST API Reference for available endpoints.


What's Next?

Jetonomy uses a universal adapter pattern for every external integration point. Instead of hard-coding a dependency on a specific search engine, email provider, membership plugin, or AI provider, each integration is represented by a PHP interface. You implement the interface, register your adapter, and Jetonomy uses it everywhere.

All adapters are managed through the static Adapter_Registry class (includes/adapters/class-adapter-registry.php).


The Four Adapter Types

Interface Class (namespace Jetonomy\Adapters\) What it controls
Search_Adapter interface-search-adapter.php Full-text search for posts, replies, spaces
Email_Adapter interface-email-adapter.php Outbound notification emails
Membership_Adapter interface-membership-adapter.php Membership level checks and gating
AI_Adapter interface-ai-adapter.php AI chat completions and embeddings (text generation, moderation, semantic features)

Built-in Adapters (Free)

Adapter Class Type Active When
Fulltext_Search (Jetonomy\Search\) Search Always (MySQL FULLTEXT - built-in)
WP_Mail_Adapter Email Always (uses wp_mail())
WP_Roles_Adapter Membership Always (WP role-based membership fallback)
MemberPress_Adapter Membership MemberPress plugin is active
PMPro_Adapter Membership Paid Memberships Pro is active
Ollama_AI_Adapter AI A local Ollama endpoint is configured

Pro Adapters (Jetonomy Pro)

Adapter Class Type Active When
WooCommerce_Adapter Membership WooCommerce Memberships is active
RCP_Adapter Membership Restrict Content Pro is active
LearnDash_Adapter Membership LearnDash is active (4.x and 5.x)
Tutor_Adapter Membership Tutor LMS is active
LifterLMS_Adapter Membership LifterLMS is active
Sensei_Adapter Membership Sensei LMS is active
MasterStudy_Adapter Membership MasterStudy LMS is active
OpenAI_AI_Adapter AI The AI extension is enabled with an OpenAI provider configured
Anthropic_AI_Adapter AI The AI extension is enabled with an Anthropic provider configured
Custom_AI_Adapter AI The AI extension is enabled with a custom OpenAI-compatible provider configured

Pro registers membership adapters via Adapter_Registry::register_membership() and AI adapters via Adapter_Registry::register_ai() at plugins_loaded priority 20.


Adapter_Registry API

// Register adapters.
\Jetonomy\Adapters\Adapter_Registry::register_search( 'my-search', $adapter );
\Jetonomy\Adapters\Adapter_Registry::register_email( 'my-mailer', $adapter );
\Jetonomy\Adapters\Adapter_Registry::register_membership( 'my-membership', $adapter );
\Jetonomy\Adapters\Adapter_Registry::register_ai( 'my-ai', $adapter );

// Retrieve the active adapter (first registered adapter where is_active() returns true).
$search     = \Jetonomy\Adapters\Adapter_Registry::get_search();
$email      = \Jetonomy\Adapters\Adapter_Registry::get_email();
$membership = \Jetonomy\Adapters\Adapter_Registry::get_membership();
$ai         = \Jetonomy\Adapters\Adapter_Registry::get_ai();

// Retrieve a specific adapter by ID.
$mp     = \Jetonomy\Adapters\Adapter_Registry::get_membership( 'memberpress' );
$openai = \Jetonomy\Adapters\Adapter_Registry::get_ai( 'openai' );

// List all registered membership / AI adapters.
$all    = \Jetonomy\Adapters\Adapter_Registry::get_all_membership();
$all_ai = \Jetonomy\Adapters\Adapter_Registry::get_all_ai();

The Registry returns null when no active adapter is found for a type - always null-check before calling methods.

Registration timing: Register your adapters at plugins_loaded. Use priority 9 if you want your adapter to override a built-in default (e.g. replacing built-in search). Use priority 15 for additive adapters that do not need to override defaults (e.g. adding a new membership source):

add_action( 'plugins_loaded', function() {
    if ( ! class_exists( '\Jetonomy\Adapters\Adapter_Registry' ) ) {
        return; // Jetonomy not active.
    }
    \Jetonomy\Adapters\Adapter_Registry::register_search(
        'meilisearch',
        new My_Plugin\Meilisearch_Adapter()
    );
}, 15 );

Search Adapter Interface

namespace Jetonomy\Adapters;

interface Search_Adapter {
    /** Return true when this adapter is ready to handle queries. */
    public function is_active(): bool;

    /** Index a document. Called when a post or reply is created or updated. */
    public function index( string $object_type, int $object_id, array $data ): void;

    /**
     * Execute a search query.
     *
     * @param string   $query      The search string.
     * @param string   $type       'post', 'reply', or 'space'.
     * @param int|null $space_id   Optional space filter.
     * @param int      $limit      Max results (default 20).
     * @param int      $offset     Pagination offset.
     * @return array               Array of result objects with at least: id, title (posts/spaces) or content (replies).
     */
    public function search( string $query, string $type, ?int $space_id, int $limit, int $offset ): array;

    /** Remove a document from the index. Called when a post or reply is deleted. */
    public function delete( string $object_type, int $object_id ): void;
}

Example: Custom Elasticsearch Adapter

<?php
namespace My_Plugin;

use Jetonomy\Adapters\Search_Adapter;

class Elasticsearch_Adapter implements Search_Adapter {

    private \Elasticsearch\Client $client;

    public function __construct() {
        // Build your Elasticsearch client here.
        $this->client = \Elasticsearch\ClientBuilder::create()
            ->setHosts( [ get_option( 'my_plugin_es_host', 'localhost:9200' ) ] )
            ->build();
    }

    public function is_active(): bool {
        // Only activate when the Elasticsearch host option is set and the client connects.
        $host = get_option( 'my_plugin_es_host' );
        return ! empty( $host ) && $this->client->ping();
    }

    public function index( string $object_type, int $object_id, array $data ): void {
        $this->client->index( [
            'index' => 'jetonomy_' . $object_type,
            'id'    => $object_id,
            'body'  => $data,
        ] );
    }

    public function search( string $query, string $type, ?int $space_id, int $limit, int $offset ): array {
        $must = [
            [ 'multi_match' => [ 'query' => $query, 'fields' => [ 'title^3', 'content' ] ] ],
        ];

        if ( $space_id ) {
            $must[] = [ 'term' => [ 'space_id' => $space_id ] ];
        }

        $params = [
            'index' => 'jetonomy_' . $type,
            'body'  => [
                'query' => [ 'bool' => [ 'must' => $must ] ],
                'from'  => $offset,
                'size'  => $limit,
            ],
        ];

        $raw = $this->client->search( $params );

        // Map Elasticsearch hits to the flat object array Jetonomy expects.
        return array_map(
            fn( $hit ) => (object) array_merge( $hit['_source'], [ 'id' => (int) $hit['_id'] ] ),
            $raw['hits']['hits'] ?? []
        );
    }

    public function delete( string $object_type, int $object_id ): void {
        $this->client->delete( [
            'index' => 'jetonomy_' . $object_type,
            'id'    => $object_id,
        ] );
    }
}

Register it:

add_action( 'plugins_loaded', function() {
    if ( ! class_exists( '\Jetonomy\Adapters\Adapter_Registry' ) ) {
        return;
    }
    \Jetonomy\Adapters\Adapter_Registry::register_search(
        'elasticsearch',
        new My_Plugin\Elasticsearch_Adapter()
    );
}, 15 );

Jetonomy will call is_active() on every registered search adapter and use the first one that returns true. Because the built-in Fulltext_Search adapter always returns true, register your custom adapter before the defaults are initialized - or override it by making sure your adapter is registered first.

The built-in defaults are initialized at plugins_loaded priority 10 via Adapter_Registry::init_defaults(). Registering at priority 15 means your adapter is added after the defaults, but since get_search() iterates in insertion order, you need to register at priority 9 if you want your adapter to take precedence:

add_action( 'plugins_loaded', function() {
    // Priority 9 - runs before Jetonomy's init_defaults() at priority 10.
    \Jetonomy\Adapters\Adapter_Registry::register_search( 'elasticsearch', new My_Plugin\Elasticsearch_Adapter() );
}, 9 );

Email Adapter Interface

namespace Jetonomy\Adapters;

interface Email_Adapter {
    public function is_active(): bool;

    /**
     * Send a single transactional email.
     *
     * @param string   $to            Recipient email address.
     * @param string   $subject       Email subject line.
     * @param string   $html          HTML body.
     * @param string   $plain         Plain-text fallback.
     * @param string[] $extra_headers Additional mail headers.
     * @return bool True on success.
     */
    public function send( string $to, string $subject, string $html, string $plain, array $extra_headers = [] ): bool;

    /**
     * Send a batch of emails.
     *
     * @param array $messages Array of ['to', 'subject', 'html', 'plain'] arrays.
     * @return array          Results array indexed by recipient.
     */
    public function send_batch( array $messages ): array;

    /** Register any hooks needed (e.g. intercepting wp_mail for logging). */
    public function register_hooks(): void;
}

Example: Postmark Adapter

class Postmark_Adapter implements \Jetonomy\Adapters\Email_Adapter {

    public function is_active(): bool {
        return ! empty( get_option( 'my_plugin_postmark_token' ) );
    }

    public function send( string $to, string $subject, string $html, string $plain, array $extra_headers = [] ): bool {
        $token = get_option( 'my_plugin_postmark_token' );
        $from  = get_option( 'admin_email' );

        $response = wp_remote_post( 'https://api.postmarkapp.com/email', [
            'headers' => [
                'Accept'                  => 'application/json',
                'Content-Type'            => 'application/json',
                'X-Postmark-Server-Token' => $token,
            ],
            'body' => wp_json_encode( [
                'From'     => $from,
                'To'       => $to,
                'Subject'  => $subject,
                'HtmlBody' => $html,
                'TextBody' => $plain,
            ] ),
        ] );

        return ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response );
    }

    public function send_batch( array $messages ): array {
        $results = [];
        foreach ( $messages as $msg ) {
            $results[ $msg['to'] ] = $this->send( $msg['to'], $msg['subject'], $msg['html'], $msg['plain'] );
        }
        return $results;
    }

    public function register_hooks(): void {
        // Optional - intercept wp_mail if you want to route ALL site email through Postmark.
    }
}

Membership Adapter Interface

namespace Jetonomy\Adapters;

interface Membership_Adapter {
    public function is_active(): bool;

    /** Return all active membership level IDs for a user. */
    public function get_user_levels( int $user_id ): array;

    /** Check whether a user has a specific membership level. */
    public function user_has_level( int $user_id, string $level_id ): bool;

    /** Return all available membership levels as ['id' => ..., 'name' => ...] objects. */
    public function get_all_levels(): array;

    /** Return the human-readable label for a level ID. */
    public function get_level_label( string $level_id ): string;

    /** Register any hooks needed for lifecycle events (e.g. activation/deactivation). */
    public function register_hooks(): void;
}

The register_hooks() method is where you fire jetonomy_membership_activated and jetonomy_membership_deactivated - see 02-hooks-reference.md.

Example: Custom Membership Adapter

class My_Membership_Adapter implements \Jetonomy\Adapters\Membership_Adapter {

    public function is_active(): bool {
        return defined( 'MY_MEMBERSHIP_VERSION' );
    }

    public function get_user_levels( int $user_id ): array {
        return (array) get_user_meta( $user_id, 'my_membership_levels', true );
    }

    public function user_has_level( int $user_id, string $level_id ): bool {
        return in_array( $level_id, $this->get_user_levels( $user_id ), true );
    }

    public function get_all_levels(): array {
        return my_membership_get_all_plans(); // Your own function.
    }

    public function get_level_label( string $level_id ): string {
        return my_membership_get_plan_name( $level_id ) ?? $level_id;
    }

    public function register_hooks(): void {
        // Fire Jetonomy's membership hooks so space access is updated automatically.
        add_action( 'my_membership_activated', function( int $user_id, string $plan_id ) {
            do_action( 'jetonomy_membership_activated', $user_id, $plan_id );
        }, 10, 2 );

        add_action( 'my_membership_cancelled', function( int $user_id, string $plan_id ) {
            do_action( 'jetonomy_membership_deactivated', $user_id, $plan_id );
        }, 10, 2 );
    }
}

AI Adapter Interface

namespace Jetonomy\Adapters;

interface AI_Adapter {
    /** Whether this adapter is configured and ready. */
    public function is_active(): bool;

    /** Unique provider identifier (e.g. 'openai', 'anthropic', 'ollama'). */
    public function get_id(): string;

    /** Human-readable provider name. */
    public function get_name(): string;

    /**
     * Send a chat completion request.
     *
     * @param array $messages Array of ['role' => 'system'|'user'|'assistant', 'content' => string].
     * @param array $options  Optional: model, temperature, max_tokens, json_mode.
     * @return array{content: string, usage: array{prompt_tokens: int, completion_tokens: int, total_tokens: int}, model: string}
     * @throws \RuntimeException On API failure.
     */
    public function chat( array $messages, array $options = [] ): array;

    /**
     * Generate embeddings for text.
     *
     * @param string $text    Input text.
     * @param array  $options Optional: model.
     * @return array{embedding: float[], model: string, usage: array{total_tokens: int}}
     * @throws \RuntimeException On API failure or if provider does not support embeddings.
     */
    public function embed( string $text, array $options = [] ): array;

    /** Return supported models as id => display_name. */
    public function get_models(): array;

    /** Test the connection (validates API key + reachability). Returns ['ok' => bool, 'error'? => string, 'model'? => string]. */
    public function test(): array;
}

The built-in Ollama_AI_Adapter (free) talks to a local Ollama endpoint and activates only when one is configured. Jetonomy Pro's AI extension registers OpenAI_AI_Adapter, Anthropic_AI_Adapter, and Custom_AI_Adapter (any OpenAI-compatible endpoint). Adapter_Registry::get_ai() returns the first registered adapter whose is_active() returns true; pass an explicit ID to target a specific provider.

Example: Custom AI Adapter

class My_AI_Adapter implements \Jetonomy\Adapters\AI_Adapter {

    public function is_active(): bool {
        return ! empty( get_option( 'my_plugin_ai_key' ) );
    }

    public function get_id(): string {
        return 'my-ai';
    }

    public function get_name(): string {
        return 'My AI Provider';
    }

    public function chat( array $messages, array $options = [] ): array {
        $response = wp_remote_post( 'https://api.example.com/v1/chat', [
            'headers' => [
                'Authorization' => 'Bearer ' . get_option( 'my_plugin_ai_key' ),
                'Content-Type'  => 'application/json',
            ],
            'body' => wp_json_encode( [
                'model'    => $options['model'] ?? 'default',
                'messages' => $messages,
            ] ),
        ] );

        if ( is_wp_error( $response ) ) {
            throw new \RuntimeException( $response->get_error_message() );
        }

        $body = json_decode( wp_remote_retrieve_body( $response ), true );

        return [
            'content' => $body['choices'][0]['message']['content'] ?? '',
            'usage'   => $body['usage'] ?? [ 'prompt_tokens' => 0, 'completion_tokens' => 0, 'total_tokens' => 0 ],
            'model'   => $body['model'] ?? ( $options['model'] ?? 'default' ),
        ];
    }

    public function embed( string $text, array $options = [] ): array {
        throw new \RuntimeException( 'Embeddings not supported.' );
    }

    public function get_models(): array {
        return [ 'default' => 'Default Model' ];
    }

    public function test(): array {
        return $this->is_active()
            ? [ 'ok' => true ]
            : [ 'ok' => false, 'error' => 'API key not configured.' ];
    }
}

Connecting Adapters to Jetonomy Events

Adapters do not self-wire - you need to connect them to Jetonomy's lifecycle hooks to trigger indexing, emailing, or broadcasting at the right time.

Search: Index content on create/update

add_action( 'jetonomy_after_create_post', function( int $post_id, int $space_id ) {
    $search = \Jetonomy\Adapters\Adapter_Registry::get_search();
    if ( ! $search ) return;

    $post = \Jetonomy\Models\Post::find( $post_id );
    if ( $post ) {
        $search->index( 'post', $post_id, [
            'title'      => $post->title,
            'content'    => wp_strip_all_tags( $post->content ),
            'space_id'   => $post->space_id,
            'author_id'  => $post->author_id,
            'created_at' => $post->created_at,
        ] );
    }
}, 10, 2 );

add_action( 'jetonomy_post_deleted', function( int $post_id ) {
    $search = \Jetonomy\Adapters\Adapter_Registry::get_search();
    $search?->delete( 'post', $post_id );
} );

AI: Summarize a new post on demand

add_action( 'jetonomy_after_create_post', function( int $post_id, int $space_id ) {
    $ai = \Jetonomy\Adapters\Adapter_Registry::get_ai();
    if ( ! $ai ) return;

    $post = \Jetonomy\Models\Post::find( $post_id );
    if ( $post ) {
        $result = $ai->chat( [
            [ 'role' => 'system', 'content' => 'Summarize the following topic in one sentence.' ],
            [ 'role' => 'user',   'content' => wp_strip_all_tags( $post->content ) ],
        ] );
        // Persist $result['content'] wherever you need it.
    }
}, 10, 2 );

Summary: Registration Cheat Sheet

add_action( 'plugins_loaded', function() {
    if ( ! class_exists( '\Jetonomy\Adapters\Adapter_Registry' ) ) {
        return;
    }

    // Search - replace built-in MySQL FULLTEXT.
    \Jetonomy\Adapters\Adapter_Registry::register_search(
        'meilisearch',
        new My_Plugin\Meilisearch_Adapter()
    );

    // Email - replace wp_mail for notification emails.
    \Jetonomy\Adapters\Adapter_Registry::register_email(
        'postmark',
        new My_Plugin\Postmark_Adapter()
    );

    // Membership - add a custom membership source.
    $adapter = new My_Plugin\My_Membership_Adapter();
    $adapter->register_hooks();
    \Jetonomy\Adapters\Adapter_Registry::register_membership( 'my-membership', $adapter );

    // AI - add a custom AI provider for chat completions / embeddings.
    \Jetonomy\Adapters\Adapter_Registry::register_ai(
        'my-ai',
        new My_Plugin\My_AI_Adapter()
    );
}, 9 ); // Priority 9 ensures search adapter runs before built-in defaults at priority 10.

What's Next?

Developer reference for the FluentCommunity coexistence integration shipped in Jetonomy 1.3.8. This page is for plugin/theme developers extending or debugging the integration. End users should start with the FluentCommunity integration guide.

What You Will Learn

  • Where the integration lives and when it loads
  • Which WordPress options persist its state
  • Which FluentCommunity and Jetonomy hooks it consumes
  • Which helpers you can reuse from your own code
  • How loop protection, identity keying, and stale-pair handling work

File Layout

The entire integration lives in one class:

includes/integrations/class-fluent-community.php

Loaded from includes/class-jetonomy.php only when FluentCommunity is active:

if ( class_exists( '\\FluentCommunity\\App\\App' ) ) {
    require_once JETONOMY_DIR . 'includes/integrations/class-fluent-community.php';
    new Integrations\Fluent_Community();
}

No autoload entry beyond this gate. Deactivate FluentCommunity and nothing from this file is parsed or instantiated.

Persisted State

Four WordPress options hold the entire integration footprint. No custom tables, no post meta, no user meta.

Option Type Default Purpose
jetonomy_fc_space_pairs array {fc_space_id: jt_space_id} [] Space pairing map. One row holds every pair.
jetonomy_fc_tab_label string Discussions Label used on the FC tab, the Jetonomy sidebar card, and the FC profile Discussions block.
jetonomy_fc_sync_members '1' / '0' '1' Toggle for bidirectional member sync.
jetonomy_fc_broadcast '1' / '0' '1' Toggle for topic broadcast to the paired FC feed.

All four options are removed on uninstall by Jetonomy's standard jetonomy_* option sweep in uninstall.php.

Reading the pair map

$pairs = get_option( 'jetonomy_fc_space_pairs', array() );
if ( is_array( $pairs ) ) {
    foreach ( $pairs as $fc_space_id => $jt_space_id ) {
        // Use as needed. Both IDs are integers when the map is valid.
    }
}

Reverse lookup

To find the Jetonomy space paired with a given FC space (or the reverse), walk the map:

$fc_id_for_jt = function ( int $jt_id ) {
    $pairs = get_option( 'jetonomy_fc_space_pairs', array() );
    if ( ! is_array( $pairs ) ) {
        return 0;
    }
    foreach ( $pairs as $fc => $jt ) {
        if ( (int) $jt === $jt_id ) {
            return (int) $fc;
        }
    }
    return 0;
};

FluentCommunity Hooks Consumed

Hook Type Surface
get_avatar_url (core WP) filter Unifies the avatar across both sides by reading wp_fcom_xprofile.avatar keyed on user_id.
fluent_community/space_header_links filter Appends the Discussions tab to the FC space header.
fluent_community/activity/after_contents_user filter Renders the Discussions block on the FC profile.
fluent_community/space/joined action Triggers member sync from FC to Jetonomy.
fluent_community/comment_added action Triggers the comment-to-reply bridge on broadcast feed comments.

All FC hook names were verified against FluentCommunity 2.3.0.

Jetonomy Hooks Consumed

Hook Type Surface
jetonomy_admin_settings_tabs action Registers the FluentCommunity settings tab.
jetonomy_admin_settings_tab_content action Renders the tab body when active.
jetonomy_sidebar_after_about action Renders the "Also on {community}" sidebar card on paired Jetonomy spaces.
jetonomy_profile_after_stats action Renders the cross-link to the member's FluentCommunity profile.
jetonomy_user_joined_space action Triggers member sync from Jetonomy to FC.
jetonomy_after_create_post action Triggers the broadcast of a new topic to the paired FC feed.

Identity Keying

Everything joins on user_id, never on username. user_id is the primary key in both wp_fcom_xprofile and wp_jt_user_profiles, so the integration stays correct no matter how usernames diverge across the two plugins.

The integration ships a static helper to map user_id to the FC username for URL construction:

// Private in the integration class. Shape reproduced here for reference.
// Returns null when the user has no FC xprofile row.
fc_username_for_user( int $user_id ): ?string

If you need this elsewhere, query wp_fcom_xprofile.username by user_id directly. The result is request-scoped cached inside the integration class.

Community Name Helper

fc_site_title(): string reads FC's configured site_title from the fluent_community_settings option and falls back to the WP site name, then to the translated string Community. This is what drives the dynamic button and card labels. Reuse the same option key if you are rendering your own cross-links:

$settings = get_option( 'fluent_community_settings', array() );
$title    = is_array( $settings ) && ! empty( $settings['site_title'] )
    ? (string) $settings['site_title']
    : get_bloginfo( 'name' );

Loop Protection

Member sync and broadcast use a static $syncing flag so a join or post on one side never triggers a boomerang write back:

// Pseudocode matching the real implementation.
if ( self::$syncing ) {
    return;
}
self::$syncing = true;
try {
    // Write to the other side.
} finally {
    self::$syncing = false;
}

For the comment-to-reply bridge, only comments on broadcast feed posts round-trip. Broadcast feed rows are tagged with a meta marker when Jetonomy creates them, and the bridge listens only for comments on rows carrying that marker. Native FC feed posts never create Jetonomy replies.

Stale Pair Handling

At render time, every surface re-resolves the paired space ID. If the referenced FC or Jetonomy space no longer exists (deleted, trashed, or the pair option references an invalid ID), the tab or card silently disappears and no admin cleanup is required. The integration never fatals on a stale pair.

Add-Only Semantics

Member sync is deliberately add-only:

  • Joins propagate in both directions.
  • Leaves do not propagate. Removing yourself from one side never yanks you out of the other.
  • Role changes do not propagate. Each plugin manages its own role structure.

If you build on top of this integration and need leave-sync or role-sync behaviour, do it in a separate extension with an explicit per-pair toggle and a visible admin warning. The defaults stay add-only to avoid accidental bulk removals.

Privacy Guard

Topics marked as private (is_private = 1) are never broadcast to FluentCommunity. The FC feed audience can be broader than the private-topic scope, so the guard prevents leaking private content to a wider audience. If you add your own broadcast surfaces on top, apply the same guard:

if ( ! empty( $post->is_private ) ) {
    return; // Skip broadcast.
}

REST Architecture Note

FluentCommunity ships as a Vue SPA consuming its REST API. All the PHP filters the integration uses run inside REST response preparation, and their output flows through to the SPA render automatically. No Jetonomy-side REST additions or JS injection are needed for v1 of the integration.

Verified against live FC endpoints at build time:

Endpoint Filter that lands output in SPA
GET /fluent-community/v2/spaces/{slug}/by-slug fluent_community/space_header_links populates header_links
GET /fluent-community/v2/profile/{username} fluent_community/profile_view_data populates profile_navs (not used in v1)
GET /fluent-community/v2/activities?... fluent_community/activity/after_contents_user appends to the user activity view

Extending

Want to build on top? Two clean extension points:

  • Listen for the broadcast. The integration calls fluent_community/feed/created after creating the FC feed row, so your code can react to broadcasts with your own handler.
  • Replace a surface. Because every render hook exits early when its pair resolves to nothing, you can remove the integration's handler from jetonomy_sidebar_after_about (priority 10) and register your own without code-level conflicts.

Destructive extensions (leave-sync, role-sync, privacy mirroring) belong in a Pro extension with explicit per-pair toggles and a backfill tool, not as drop-in replacements for the free integration.

Developer reference for the BuddyPress coexistence integration. This page is for plugin/theme developers extending or debugging the integration. End users should start with the BuddyPress integration guide.

What You Will Learn

  • Where the integration lives and when it loads
  • What state persists (group meta + options) and where
  • Which BuddyPress and Jetonomy hooks the integration consumes
  • How the activity broadcast and comment-to-reply bridge work, and how to extend them
  • How loop protection, identity keying, and stale-pair handling are implemented

File Layout

The integration lives in a single class:

includes/integrations/class-buddypress.php

Loaded from includes/class-jetonomy.php only when the BuddyPress Groups component is active:

if ( function_exists( 'bp_is_active' ) && bp_is_active( 'groups' ) ) {
    require_once JETONOMY_DIR . 'includes/integrations/class-buddypress.php';
    new Integrations\BuddyPress();
}

The broadcast and comment-bridge methods additionally gate themselves on bp_is_active( 'activity' ) at runtime, so a BP install with Groups but not Activity stays fatal-free.

Persisted State

Where Key Type Purpose
BP group meta jetonomy_space_id int Points a group at its paired Jetonomy space. One value per group.
WP option jetonomy_bp_broadcast '1' / '0' Toggle for JT topic → BP group activity broadcast. Defaults on.
WP option jetonomy_bp_comment_bridge '1' / '0' Toggle for BP activity comment → JT reply bridge. Defaults on.
BP activity meta jetonomy_post_id int Tags a broadcast activity row with its originating Jetonomy post ID. The comment bridge reads this to decide which activity comments should round-trip as JT replies.

Class constants

BuddyPress::META_KEY              = 'jetonomy_space_id';
BuddyPress::OPT_BROADCAST         = 'jetonomy_bp_broadcast';
BuddyPress::OPT_COMMENT_BRIDGE    = 'jetonomy_bp_comment_bridge';
BuddyPress::ACTIVITY_META_POST    = 'jetonomy_post_id';
BuddyPress::ACTIVITY_TYPE         = 'jetonomy_topic';

Reading the pair

$space_id = (int) groups_get_groupmeta( $group_id, \Jetonomy\Integrations\BuddyPress::META_KEY, true );

Reverse lookup

$group_id = \Jetonomy\Integrations\BuddyPress::find_group_by_space( $space_id );

This runs a single meta-keyed query, no get_posts() loop.

BuddyPress Hooks Consumed

Group lifecycle

Hook Handler Note
groups_created_group on_group_created + save_group_forum_settings_on_create Reads the jt_bp_forum_action form field ('create', 'link_{id}', 'none'). Only creates a new space when explicitly requested.
groups_delete_group on_group_deleted Unlinks the space. Space itself is preserved.
groups_details_updated on_group_updated Syncs name, description, and visibility (public/private/hidden) to the paired space.

Member sync

Hook Handler Direction
groups_join_group on_member_join BP → JT
groups_leave_group on_member_leave BP → JT
groups_remove_member on_member_leave BP → JT
groups_ban_member on_member_leave BP → JT
groups_unban_member on_member_join BP → JT
groups_promote_member on_member_promote BP → JT (admin/mod)
groups_demote_member on_member_demote BP → JT (back to member)

Activity

Hook Handler Note
bp_register_activity_actions register_activity_type Registers the jetonomy_topic activity type with bp_activity_set_action so BP renders it alongside native types.
bp_activity_comment_posted on_bp_activity_comment_posted Runs the comment-to-reply bridge when the parent activity carries the broadcast meta marker.
bp_activity_allowed_tags filter_broadcast_allowed_tags Adds <br> and <p> to BP's kses allowlist so broadcast paragraphs survive save AND display. Both tags carry no attributes, so no XSS surface.

Render

Hook Handler
bp_setup_nav (priority 20) register_group_forum_tab + register_profile_forum_tab
groups_custom_group_fields_editable render_group_forum_settings (the dropdown in Create + Manage > Details)
groups_group_details_edited save_group_forum_settings
bp_after_group_details_creation_step render_group_forum_settings (creation step)

Jetonomy Hooks Consumed

Hook Handler Surface
jetonomy_before_content render_back_to_group_banner Renders the "← Group Name" link at the top of paired space / topic pages.
jetonomy_sidebar_about_after_meta render_sidebar_group_link Renders the small tag in the sidebar About card linking back to the BP group.
jetonomy_user_joined_space not directly hooked; member sync is BP → JT only (BP is the source of truth for group membership). n/a
jetonomy_after_create_post on_jt_post_created_for_bp Triggers the broadcast to the paired BP group activity stream.

Activity Broadcast Flow

On jetonomy_after_create_post:

  1. If broadcast is disabled or no pair exists for the space, return.
  2. If the post is private (is_private), return.
  3. If the BP Activity component is not active, return.
  4. Build the activity body: excerpt converted to <p> paragraphs with block-level tag boundaries preserved, plus a trailing "Shared from the forum · View discussion" attribution line.
  5. Call bp_activity_add with component=groups, type=jetonomy_topic, item_id=$group_id, secondary_item_id=$post_id, and hide_sitewide set when the group is not public.
  6. Store the post ID in activity meta: bp_activity_update_meta( $activity_id, 'jetonomy_post_id', $post_id ).

The bp_activity_allowed_tags filter that whitelists <br> and <p> is attached globally while broadcast is enabled. BP runs kses both on save and on display, so a per-call toggle would strip the tags when the activity is rendered later.

Comment-to-Reply Bridge Flow

On bp_activity_comment_posted( $comment_id, $r, $activity ):

  1. If the loop-guard flag is set, return. Prevents boomerang writes.
  2. If bp_activity_get_meta( $activity->id, 'jetonomy_post_id' ) is empty, the parent activity is not one of ours, return.
  3. Load the Jetonomy post; if it is not published, return (the broadcast survives, but we do not create replies against draft/trashed topics).
  4. Build the reply content: wp_kses_post on the comment HTML for display, wp_strip_all_tags for the plain version.
  5. Create the reply via Reply::create with the same author as the BP commenter.

Edits and deletes on BP do NOT propagate. The JT thread is the durable record.

Loop Protection

A shared static $syncing flag stops a write on one side from triggering a boomerang write back. Every member-sync, broadcast, and bridge method flips it for the duration of the write:

self::$syncing = true;
// do the write that might fire hooks we listen to
self::$syncing = false;

Both the group-lifecycle handlers (on_group_created, on_group_updated) and the member-sync handlers read self::$syncing at entry.

Identity Keying

Everything joins on user_id. BP member profiles and Jetonomy user profiles share the same WP user ID, so username divergence is not a problem.

Stale Pair Handling

Every render hook resolves the paired entity lazily. If the paired space no longer exists when the forum tab is about to render, the tab callback returns early without emitting markup. The same pattern applies to the sidebar link and back-banner.

Extending

Three clean extension points:

  • Disable member-leave propagation. Remove the groups_leave_group, groups_remove_member, and groups_ban_member actions from the integration at init + 30 or later if you want the add-only semantics the FluentCommunity integration uses.
  • Custom activity rendering. Filter bp_activity_action_before_save or add a filter on bp_get_activity_action to override how jetonomy_topic rows render without touching the integration.
  • Custom permission gate on forum tab. Filter bp_is_user_in_group (or call groups_is_user_member directly) inside your own hook handler on register_group_forum_tab (priority < 20) to restrict the Forum tab to certain roles.

Destructive or privacy-affecting extensions (forcing role sync one-way, propagating flags cross-surface, cascading deletes) belong in a Pro extension with explicit per-pair toggles, not as drop-in replacements for the free integration.

Jetonomy 1.4.1 introduced the Public / Private community toggle documented in Access Control Settings. On the code side that toggle is enforced through two pieces:

  1. A small helper class - Jetonomy\Visibility - that every front-end template and REST permission callback can call to decide "is this caller allowed to see community content right now?"
  2. A shell script - bin/access-matrix-check.sh - that walks every public REST route as every role in both modes and asserts the responses match the documented contract.

This page covers both.

Namespace: Jetonomy\ **Source:** includes/class-visibility.php, bin/access-matrix-check.sh


Why the helper exists

Before 1.4.1 the public/private check was sprinkled across templates, the template loader, and individual REST controllers. Each call site had its own "is the community private and the user a guest?" expression, and any new endpoint had to remember to repeat the pattern. The fastest way to ship a leak was to forget the check.

Jetonomy\Visibility consolidates that into one read of jetonomy_settings.guest_read. The front-end template loader and every public-read REST endpoint route through the same helper, so they answer the same question with the same logic. New endpoints opt into the gate with a single line.

The helper deliberately does not look at per-resource visibility (private spaces, blocked users, restricted posts). Those remain the responsibility of individual controllers - Visibility only answers the global "can this caller see ANY community content right now?" question.


API Reference

Visibility::can_view_community()

Returns true if the current request should be allowed to see community content, false otherwise. In public mode this is always true. In private mode it requires the caller to be authenticated.

Returns: bool

Example - gating a custom template fragment:

add_action( 'jetonomy_sidebar_before', function () {
    if ( ! \Jetonomy\Visibility::can_view_community() ) {
        return;
    }
    echo do_shortcode( '[my_member_only_widget]' );
} );

The check is global, not per-resource - you do not need to pass a user ID or a post ID. Per-resource visibility (private spaces, restricted access rules) is enforced separately inside the relevant controllers.


Visibility::get_mode()

Returns the active visibility mode as a string. Useful when you want to render a different UI in private mode (e.g. a "Members only" badge in the site header) without duplicating the option read.

Returns: string - 'public' or 'private'

Example - tagging the body class:

add_filter( 'body_class', function ( array $classes ): array {
    $classes[] = 'jt-mode-' . \Jetonomy\Visibility::get_mode();
    return $classes;
} );

The function defaults to 'public' on a fresh install - an unset, null, or true value of guest_read all resolve to public. Only an explicit false flips the community to private.


Visibility::rest_check()

Designed to be used as a REST permission_callback. Returns true when the caller may proceed, or a 401 WP_Error with code community_private when the community is in private mode and the caller is not logged in.

Returns: true|\WP_Error

Example - protecting your own REST route:

register_rest_route( 'my-plugin/v1', '/community-events', [
    'methods'             => 'GET',
    'callback'            => 'my_plugin_list_events',
    'permission_callback' => [ '\Jetonomy\Visibility', 'rest_check' ],
] );

Example - chaining with an existing capability check:

'permission_callback' => static function ( $request ) {
    $vis = \Jetonomy\Visibility::rest_check( $request );
    if ( is_wp_error( $vis ) ) {
        return $vis;
    }
    return current_user_can( 'read' );
},

Anonymous calls in private mode return:

{
    "code": "community_private",
    "message": "This community is private. Please log in to view content.",
    "data": { "status": 401 }
}

Authenticated calls fall through to your route's own permission logic.

Note: /auth/* and /admin/* endpoints intentionally do not route through this helper. Locking /auth/* would lock users out forever, and admin endpoints have their own capability gates. Apply Visibility::rest_check to public-read endpoints only.


Per-resource visibility (1.8.0)

Visibility answers the global "can this caller see ANY community content?" question. The rules below sit underneath it and decide which individual spaces and posts a viewer may see. As of 1.8.0 they route through shared primitives so REST, the web, and the app cannot disagree.

One space-listing predicate for every surface

Space::listing_visibility_sql( ?int $user_id, string $alias = '' ) (includes/models/class-space.php:254) is the single SQL predicate every listing surface applies - the community home, search, full-text, Space::list_visible() (class-space.php:524), and REST GET /spaces (includes/api/class-spaces-controller.php:349). Before 1.8.0 the REST listing hand-rolled a "public OR member" rule while the web went through this predicate, so the two drifted (measured live: 32 spaces on the web vs 26 from REST for the same viewer). They now share this one predicate.

What it resolves to:

  • Guest (user_id <= 0): public spaces only.
  • Member: public + private spaces, plus any hidden space they belong to.
  • Admin (manage_options): all spaces (1=1).

Owners can widen or narrow the rule per site with the jetonomy_space_listing_visibility_sql filter, which now reaches REST too rather than only half the surfaces.

private and hidden are distinct states (Space::visibility_levels(), class-space.php:653): a private space is discoverable but unreadable (its card shows, its posts do not); a hidden space is neither - only members can find it. A discoverable card does not leak content: post visibility stays gated separately.

Hidden spaces are front-end-manageable and forced invite-only

Both front-end space forms - create (templates/views/new-space.php) and edit (templates/views/space-edit.php) - offer all three visibility levels through \Jetonomy\space_visibility_options() (includes/functions.php:521), which is derived from Space::visibility_levels() so no surface can drift out of sync. A hidden space is forced onto the invite join policy by Space::validate_visibility_join_policy() (class-space.php:689) - a hidden space with an open/approval policy is a contradiction (the listing hides it while the gate lets anyone with the slug self-join). Front-end invite-link generation lives on the members page (templates/views/space-members.php), so an owner never has to drop into wp-admin to invite someone into a hidden space.

Post read gate: trashed/pending content returns 403

Permission_Engine::can_read_post( int $user_id, object $post ) (includes/permissions/class-permission-engine.php:357) is the one status gate, hoisted in 1.8.0 out of single-post.php so REST, oEmbed, JSON-LD and the updates poller all inherit it. Deleting a post is a soft delete (status set to trash, row kept), so any non-publish post - trashed or sitting in the moderation queue - returns false to anyone who is neither its author nor a space moderator. On GET /posts/{id} that surfaces as a 403 (includes/api/class-posts-controller.php:360). The author and moderators keep access, matching the established single-post contract.

Blocked-author content is tombstoned server-side

When a viewer has blocked a post's author, the post's title, content, and content_plain are emptied server-side by Post::apply_block_tombstone() (includes/models/class-post.php:241), applied to the row inside prepare_post() before any field is read (class-posts-controller.php:1136). The blocked_author flag on the response is therefore advisory only (class-posts-controller.php:1236): a client that ignores it still cannot render the blocked author's words. This runs on the by-id / by-slug (deep-link) paths that bypass the list-query SQL filters; list surfaces already exclude blocked authors in SQL. Guests block nobody, so a crawler sees byte-identical public output.


The Access Matrix Runner

bin/access-matrix-check.sh is the regression safety net that proves the helper actually works. It walks a representative subset of every Jetonomy REST route as every role, makes the real HTTP call, and asserts the response code matches the documented expectation.

What it covers

  • 78 checks across the public REST surface
  • 6 roles - anonymous, subscriber, author, editor (space-admin), moderator, administrator
  • Both modes - the runner can flip jetonomy_settings.guest_read to private for the duration of the run and restore it on exit (even if a check fails midway)

In public mode the runner asserts that anonymous reads return 200. In private mode it asserts the same anonymous reads return 401 from the central Visibility::rest_check gate. Logged-in calls are mode-independent and stay unchanged in either mode.

Running it locally

# From the plugin root:
bin/access-matrix-check.sh                # public mode (default)
bin/access-matrix-check.sh --mode=private # private community gate
bin/access-matrix-check.sh --quiet        # only show failures

Sample output:

PASS  GET   /spaces             [anon]       expected=200  got=200
PASS  GET   /spaces             [subscriber] expected=200  got=200
PASS  POST  /posts/123/vote     [anon]       expected=401  got=401
FAIL  GET   /posts/recent       [anon]       expected=401  got=200  <- regression

Summary: 77 passed, 1 failed (78 total)

A regression is any row where the actual response code does not match the documented expectation. Exit code is 0 on a clean run, 1 if any row fails.

Release gating

bin/build-release.sh invokes the access matrix runner before producing the release zip. If any row regresses, the build aborts and no zip is produced - the gate exists so we never ship a release that quietly opens up a previously-locked endpoint or quietly locks a previously-open one.

Run the matrix locally before every commit that touches permission_callback wiring, the Visibility helper, or REST route registration.


What's Next?

Jetonomy ships a small JavaScript toolkit that replaces native window.confirm, window.alert, and window.prompt with branded, accessible modal dialogs. The toolkit lives in assets/js/jetonomy-modals.js and is enqueued on every community page and inside wp-admin.

Globals: window.jetonomyConfirm, window.jetonomyAlert, window.jetonomyPrompt Localisation contract: window.jetonomyModalsI18n (added in 1.4.2) Source: assets/js/jetonomy-modals.js


Why the toolkit exists

Native browser dialogs are ugly, untranslatable, blocking, not styleable, and inconsistent across browsers - some hide them, some show "Prevent this page from creating additional dialogs" checkboxes, mobile Safari renders them at the bottom of the viewport. They also bypass any focus-management or screen-reader contract that the rest of the community UI follows.

The toolkit gives you:

  • A consistent visual language that matches Jetonomy's --jt-* token system
  • Promise-based async / await syntax instead of blocking calls
  • Keyboard support - ESC dismisses, Enter confirms (or commits a single-line prompt)
  • Backdrop click to cancel
  • Focus trapping while the modal is open, focus restoration on close
  • Built-in translatable labels via window.jetonomyModalsI18n

Every custom JS that ships in the Jetonomy ecosystem should use these globals instead of native dialogs. That includes Pro extensions, theme bridge plugins, and third-party integrations that hook into community pages.


API Reference

jetonomyConfirm( message, opts? )

Asks the user a yes/no question. Resolves true when the user confirms, false when they cancel, press ESC, or click the backdrop.

Parameters

Parameter Type Description
message string The body text shown inside the modal
opts.title string? Heading text. Omit for a body-only modal
opts.confirmLabel string? Confirm button label. Defaults to the localised "Confirm" string
opts.cancelLabel string? Cancel button label. Defaults to the localised "Cancel" string
opts.danger boolean? When true, the confirm button uses the danger style (red). Use for destructive actions

Returns: Promise<boolean>

Example:

const proceed = await window.jetonomyConfirm(
    'Delete this post? This cannot be undone.',
    {
        title:        'Delete post',
        confirmLabel: 'Delete',
        cancelLabel:  'Keep it',
        danger:       true,
    }
);

if ( ! proceed ) {
    return;
}
await myPlugin.deletePost( postId );

jetonomyAlert( message, opts? )

Shows a message and waits for the user to dismiss it. Resolves true once the user clicks the confirm button, presses Enter, presses ESC, or clicks the backdrop.

Parameters

Parameter Type Description
message string The body text shown inside the modal
opts.title string? Heading text
opts.confirmLabel string? Confirm button label. Defaults to "OK"

Returns: Promise<true>

Example:

await window.jetonomyAlert(
    'Your changes have been saved.',
    { title: 'Saved' }
);

// Code here runs after the user dismisses the dialog.
location.reload();

jetonomyPrompt( message, opts? )

Asks the user for a string input. Resolves the submitted string on submit, or null on cancel / ESC / backdrop click.

Parameters

Parameter Type Description
message string The body text shown above the input field
opts.title string? Heading text
opts.placeholder string? Placeholder text shown inside the empty input
opts.defaultValue string? Pre-filled value. The input is auto-selected on open so the user can overwrite or accept it
opts.multiline boolean? When true, renders a <textarea> instead of an <input type="text">
opts.confirmLabel string? Submit button label. Defaults to "Submit"
opts.cancelLabel string? Cancel button label. Defaults to "Cancel"

Returns: Promise<string|null> - the input value on submit, null on cancel

Example:

const reason = await window.jetonomyPrompt(
    'Tell us why you are reporting this reply.',
    {
        title:        'Report reply',
        placeholder:  'Optional context for the moderator team',
        multiline:    true,
        confirmLabel: 'Submit report',
    }
);

if ( reason === null ) {
    return; // User cancelled.
}

await myPlugin.reportReply( replyId, reason );

Single-line prompts commit on Enter; multiline prompts require an explicit button click (Enter inserts a newline as expected).


End-to-end Example

A custom moderator action that confirms, prompts for a note, then alerts on success - all without touching a native dialog:

async function quarantinePost( postId ) {
    const proceed = await window.jetonomyConfirm(
        'Quarantine this post? It will be hidden from public view until reviewed.',
        {
            title:        'Quarantine post',
            confirmLabel: 'Quarantine',
            danger:       true,
        }
    );

    if ( ! proceed ) {
        return;
    }

    const note = await window.jetonomyPrompt(
        'Add a moderator note (optional).',
        {
            title:        'Moderator note',
            placeholder:  'e.g. flagged for review by 3 members',
            multiline:    true,
            confirmLabel: 'Save note',
        }
    );

    if ( note === null ) {
        return; // User backed out at the note stage.
    }

    await fetch( `/wp-json/my-plugin/v1/quarantine/${ postId }`, {
        method:  'POST',
        headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': wpApiSettings.nonce },
        body:    JSON.stringify( { note } ),
    } );

    await window.jetonomyAlert(
        'Post quarantined. Moderators have been notified.',
        { title: 'Done' }
    );
}

The user gets three branded, translatable, keyboard-friendly modals in sequence instead of three jarring native dialogs.


Localisation: window.jetonomyModalsI18n

Jetonomy localises four default button labels onto window.jetonomyModalsI18n via wp_localize_script. The script is registered on both the front-end and inside wp-admin, so the global is reliably available wherever the toolkit JS is loaded.

Shape:

window.jetonomyModalsI18n = {
    cancel:  'Cancel',  // Default cancel button label (jetonomyConfirm, jetonomyPrompt)
    confirm: 'Confirm', // Default confirm button label (jetonomyConfirm)
    submit:  'Submit', // Default submit button label (jetonomyPrompt)
    ok:      'OK',      // Default OK button label (jetonomyAlert)
};

The values are translated through WordPress's standard i18n pipeline - if your site loads a jetonomy translation file, the strings arrive pre-translated and the modals adopt the active locale automatically.

How third-party callers should use it

The global is provided so you can read the localised strings when you need additional context inside your own UI (for example, to label a custom action that mirrors one of the toolkit buttons):

const cancelLabel = ( window.jetonomyModalsI18n && window.jetonomyModalsI18n.cancel ) || 'Cancel';

myDropdown.appendChild( makeMenuItem( cancelLabel, onCancel ) );

Do not override the global. Overrides are not supported and may be reset on the next page load. If you want a different label on a single modal, pass it via the per-call opts.confirmLabel / opts.cancelLabel instead:

// Correct - per-call override
await window.jetonomyConfirm( 'Publish?', { confirmLabel: 'Publish now', cancelLabel: 'Keep as draft' } );

// Wrong - mutating the global
window.jetonomyModalsI18n.confirm = 'Publish now'; // do not do this

This keeps the global predictable for every other piece of code that reads from it.


What's Next?

Jetonomy ships a full WP-CLI surface covering every core domain of the plugin: 15 command roots in the free plugin and 15 command roots in Jetonomy Pro, totalling 75+ subcommands across both plugins.

The 15 free roots are the 14 domain commands listed under Free Commands below (category, space, post, reply, vote, flag, member, mod, notification, config, tag, user, privacy, scenario), plus the standalone qa-actions command, documented under Testing and QA Commands.

All free commands live under wp jetonomy <subject> <subcommand>. All Pro commands live under wp jetonomy-pro <subject> <subcommand> (note the separate root - Pro commands require both plugins active).

On this local dev install you must prefix every wp call with --path:

wp --path="/path/to/wp" jetonomy space list

Customers running from their WordPress root directory can omit --path.


Free Commands

category

Manage top-level categories that group spaces.

Subcommand Description
create Create a new category
list List all categories
update <id> Update category fields
delete <id> Delete a category

Flags - create: --name=<name> --slug=<slug> [--description=<text>] [--parent=<id>] [--format=<format>]

Flags - update: [--name=<name>] [--slug=<slug>] [--description=<text>] [--parent=<id>] [--sort=<order>] [--format=<format>]

wp jetonomy category create --name="General" --slug=general
wp jetonomy category create --name="Support" --slug=support --parent=1
wp jetonomy category update 3 --name="Renamed" --sort=10
wp jetonomy category list --format=json

space

Create and manage spaces (forums, Q&A boards, ideas boards, feeds).

Subcommand Description
create Create a new space
list List spaces
update <id> Update space settings
delete <id> Delete a space
add-member Add a member to a space
remove-member Remove a member from a space

Flags - create: --title=<title> --slug=<slug> --category=<id> [--description=<text>] [--type=<type>] [--visibility=<vis>] [--join-policy=<policy>] [--format=<format>]

Types: forum (default), qa, ideas, feed. Visibility: public, private, hidden. Join policy: open, approval, invite.

Flags - update: All optional - [--title=<title>] [--description=<text>] [--type=<type>] [--visibility=<vis>] [--join-policy=<policy>] [--status=<status>] [--format=<format>]

wp jetonomy space create --title="General" --slug=general --category=1
wp jetonomy space create --title="Q&A" --slug=qa --category=1 --type=qa --visibility=private --join-policy=approval
wp jetonomy space update 5 --visibility=private
wp jetonomy space list --format=table

post

Create and manage posts within spaces.

Subcommand Description
create Create a new post
list List posts
update <id> Update a post
delete <id> Delete a post

Flags - create: --space=<id> --author=<id> --title=<title> --content=<content> [--status=<status>] [--slug=<slug>] [--format=<format>]

Status values: published (default), draft.

Flags - update: [--title=<title>] [--content=<content>] [--status=<status>] [--slug=<slug>] [--format=<format>]

wp jetonomy post create --space=5 --author=3 --title="Hello" --content="First post"
wp jetonomy post create --space=5 --author=3 --title="Draft" --content="..." --status=draft
wp jetonomy post update 42 --title="New title"
wp jetonomy post list --format=json

reply

Create and manage replies on posts.

Subcommand Description
create Create a reply
list List replies for a post
update <id> Update a reply
delete <id> Delete a reply
accept Mark a reply as the accepted answer

Flags - create: --post=<id> --author=<id> --content=<content> [--parent=<id>] [--status=<status>] [--format=<format>]

Flags - accept: --post=<id> --reply=<id> [--format=<format>]

wp jetonomy reply create --post=42 --author=3 --content="Great idea"
wp jetonomy reply create --post=42 --author=3 --content="Nested reply" --parent=17
wp jetonomy reply accept --post=42 --reply=17

tag

Manage post tags.

Subcommand Description
create Create a tag
list List all tags
update <id> Update a tag
delete <id> Delete a tag
get-by-slug Fetch a tag by its slug
attach Attach a tag to a post
detach Detach a tag from a post
list-for-post List tags on a specific post

Flags - create: --name=<name> [--format=<format>]

Flags - get-by-slug: --slug=<slug> [--format=<format>]

Flags - attach / detach: --post=<id> --tag=<id> [--format=<format>]

Flags - list-for-post: --post=<id> [--format=<format>] [--fields=<fields>]

wp jetonomy tag create --name="announcement"
wp jetonomy tag attach --post=42 --tag=7
wp jetonomy tag detach --post=42 --tag=7
wp jetonomy tag list-for-post --post=42

user

Manage community users - ban/unban, trust levels, profiles, and reputation.

Subcommand Description
create Create a new WordPress + Jetonomy user
ban <id> Ban a user site-wide
unban <id> Remove a site-wide ban
promote <id> Increase a user's trust level by one step
demote <id> Decrease a user's trust level by one step
set-trust <id> Set a user's trust level to an exact value (0-5)
get-trust <id> Read a user's current trust level
update-profile <id> Update display name, bio, or avatar URL
adjust-reputation <id> Add or subtract reputation points

Flags - create: --login=<login> --email=<email> [--password=<pw>] [--role=<role>] [--trust-level=<0-5>] [--display-name=<name>] [--format=<format>]

Flags - set-trust: --level=<0-5> [--format=<format>]

Flags - update-profile: [--display-name=<name>] [--bio=<bio>] [--avatar-url=<url>] [--format=<format>]

Flags - adjust-reputation: --delta=<int> (use negative values to subtract) [--format=<format>]

wp jetonomy user create --login=alice --email=alice@example.com
wp jetonomy user create --login=mod1 --email=m1@ex.com --trust-level=4 --role=editor
wp jetonomy user set-trust 42 --level=3
wp jetonomy user adjust-reputation 42 --delta=25
wp jetonomy user adjust-reputation 42 --delta=-10
wp jetonomy user update-profile 42 --bio="Hello world" --avatar-url="https://example.com/a.png"

member

Manage space membership - join, leave, and role assignment.

Subcommand Description
add Add a user to a space (admin shortcut)
remove Remove a user from a space
promote Promote a member to moderator
demote Demote a member from moderator
join Record a user joining a space (respects join policy)
leave Record a user leaving a space
set-role Set a member's role directly
is-member Check whether a user is a member

Note: --by is used for the user ID in all subcommands because --user is a reserved WP-CLI global flag.

Flags - join: --space=<id> --by=<user_id> [--role=<role>] [--format=<format>]

Flags - set-role: --space=<id> --by=<user_id> --role=<role> [--format=<format>]

Flags - is-member: --space=<id> --by=<user_id> [--format=<format>]

wp jetonomy member join --space=15 --by=4
wp jetonomy member join --space=15 --by=4 --role=moderator
wp jetonomy member set-role --space=15 --by=4 --role=moderator
wp jetonomy member leave --space=15 --by=4
wp jetonomy member is-member --space=15 --by=4

vote

Cast and inspect votes on posts and replies.

Subcommand Description
create Cast a vote (upvote or downvote)
delete Retract a vote
list List votes on a post or reply
cast Alias for create with explicit flags

Note: --voter is used for the voter's user ID because --user is a reserved WP-CLI global flag.

Flags - cast: --voter=<id> --type=<type> --id=<id> --value=<value> [--format=<format>]

Type: post or reply. Value: 1 (upvote) or -1 (downvote).

wp jetonomy vote cast --voter=3 --type=post --id=42 --value=1
wp jetonomy vote cast --voter=3 --type=reply --id=17 --value=-1

flag

Create and manage content flags (user reports).

Subcommand Description
list List flags (filterable by status)
resolve Resolve or dismiss a flag
create File a new flag against a post or reply

Note: --reporter is used for the reporting user ID because --user is reserved.

Flags - create: --type=<type> --id=<id> --reporter=<id> --reason=<reason> [--description=<description>] [--format=<format>]

Type: post or reply. Reason values: spam, harassment, off-topic, other.

wp jetonomy flag create --type=post --id=42 --reporter=3 --reason=spam
wp jetonomy flag create --type=reply --id=17 --reporter=3 --reason=harassment --description="Context here"
wp jetonomy flag list --format=json

notification

Trigger and inspect notifications.

Subcommand Description
send Trigger a notification event
list List notifications for a user
trigger Alias for send with explicit flags
mark-read Mark notifications as read for a user

Note: --to is used for the recipient user ID because --user is reserved.

Flags - trigger: --type=<type> --to=<user_id> --actor=<user_id> --object-type=<type> --object-id=<id> --message=<text> [--format=<format>]

Flags - list: --to=<user_id> [--limit=<n>] [--offset=<n>] [--fields=<fields>] [--format=<format>]

wp jetonomy notification trigger --type=reply_to_post --to=1 --actor=2 --object-type=post --object-id=5 --message="Someone replied"
wp jetonomy notification list --to=1
wp jetonomy notification list --to=1 --limit=5 --format=json

config

Read and write Jetonomy settings using dotted key paths.

Subcommand Description
get Read a setting value
set Write a setting value
list List all settings
reset Reset a single key to its default
reset-all Reset all settings to defaults
keys List available keys under a parent path

Flags - get: [--key=<dotted_path>] [--format=<format>]

Flags - set: --key=<dotted_path> --value=<value> [--format=<format>]

Flags - keys: [--key=<parent_path>] [--format=<format>] [--fields=<fields>]

wp jetonomy config get --key=trust_thresholds.1.posts
wp jetonomy config get --key=rate_limits --format=json
wp jetonomy config set --key=trust_thresholds.1.posts --value=7
wp jetonomy config set --key=notification_defaults.mention.email --value=false
wp jetonomy config keys --key=trust_thresholds
wp jetonomy config list

mod

Advanced moderation actions: bans, flag management, and content decisions.

Subcommand Description
approve Approve flagged content
spam Mark content as spam
trash Trash content
flags List flags with optional status filter
resolve Resolve or dismiss a flag
ban Ban a user (site-wide or space-scoped)
unban Lift a ban
is-banned Check whether a user is banned

Note: --target is used for the affected user and --issuer for the moderator, avoiding the reserved --user flag.

Flags - flags: [--status=<status>] [--format=<format>] [--fields=<fields>]

Status values: valid, dismissed.

Flags - resolve: --resolver=<user_id> --decision=<decision> [--format=<format>]

Flags - ban: --target=<user_id> --issuer=<user_id> [--type=<type>] [--space=<id>] [--reason=<text>] [--expires=<datetime>] [--format=<format>]

Flags - is-banned: --target=<user_id> [--space=<id>] [--format=<format>]

wp jetonomy mod flags --status=valid
wp jetonomy mod resolve 42 --resolver=1 --decision=valid
wp jetonomy mod resolve 17 --resolver=1 --decision=dismissed
wp jetonomy mod ban --target=5 --issuer=1 --reason="spam"
wp jetonomy mod ban --target=5 --issuer=1 --type=space_ban --space=3
wp jetonomy mod ban --target=5 --issuer=1 --type=silence --expires="2026-05-01 00:00:00"
wp jetonomy mod unban 5
wp jetonomy mod is-banned --target=5 --space=3

privacy

Find and remediate orphaned user data - rows still pointing at WordPress accounts that no longer exist. Use this for GDPR cleanup of data left behind by accounts deleted before Jetonomy 1.7.1, when neither the user-delete path nor the multisite removal hooks purged every Jetonomy table (notably raw AI prompt/response logs).

Subcommand Description
scan Report orphaned user data (read-only)
purge-orphans Purge all data belonging to accounts that no longer exist

scan is read-only. It lists each (table, column) still holding rows whose user id is absent from wp_users, plus the total orphan row and account counts. Rows with user id 0 are never reported - 0 is the anonymized/system sentinel that the eraser and delete path deliberately leave behind.

purge-orphans replays the live user-deletion path (it fires the jetonomy_purge_orphan_user action, the same one free and Pro already listen to via on_user_delete()), so denormalized counters are recomputed and caches busted exactly as a real deletion would - there is no second, drift-prone cleanup list. It is idempotent: purging an orphan removes the rows that made it discoverable, so a second run finds nothing and does nothing. Run --dry-run first to preview what would be removed. On multisite it cleans the current site's tables; pass --url=<site> per site to sweep a network.

Flags - scan: [--format=<format>]

Format values: table (default), json, csv.

Flags - purge-orphans: [--dry-run] (report what would be removed without removing anything) [--format=<format>]

Format values: table (default), json.

wp jetonomy privacy scan
wp jetonomy privacy scan --format=json
wp jetonomy privacy purge-orphans --dry-run
wp jetonomy privacy purge-orphans

scenario

Run end-to-end PHP scenarios that exercise full user journeys against the live database.

Subcommand Description
run <name> Run a named scenario
list List all available scenarios

Flags - run: [--cleanup] (roll back all data created by the scenario) [--format=<format>]

Flags - list: [--format=<format>] [--fields=<fields>]

Bundled scenarios: basic-forum-flow, notification-delivery-sweep, multi-user-voting-thread, moderation-lifecycle, space-member-journey.

wp jetonomy scenario list
wp jetonomy scenario list --format=json
wp jetonomy scenario run basic-forum-flow
wp jetonomy scenario run notification-delivery-sweep --cleanup
wp jetonomy scenario run multi-user-voting-thread --format=json

Pro Commands

Pro commands use wp jetonomy-pro as the top-level namespace. Every Pro subcommand requires both Jetonomy (free) and Jetonomy Pro to be active.


extension

Enable, disable, and inspect Pro extensions.

Subcommand Description
list List all extensions and their current state
enable <id> Enable an extension
disable <id> Disable an extension
activate <id> Activate (boot) an extension that is already enabled
deactivate <id> Deactivate a running extension
status <id> Show status for a single extension
wp jetonomy-pro extension list
wp jetonomy-pro extension list --format=json
wp jetonomy-pro extension enable private-messaging
wp jetonomy-pro extension disable polls
wp jetonomy-pro extension status webhooks

custom-fields

Manage custom fields that extend post or user objects.

Subcommand Description
list List all defined custom fields
create Define a new custom field
delete <id> Remove a custom field definition

Flags - create: --key=<slug> --label=<text> --type=<type> --applies-to=<target> [--description=<text>] [--options=<csv>] [--required] [--default=<value>] [--format=<format>]

Types: text, textarea, select, checkbox, number, url. Applies-to: post or user.

wp jetonomy-pro custom-fields create --key=company --label=Company --type=text --applies-to=user
wp jetonomy-pro custom-fields create --key=priority --label=Priority --type=select --applies-to=post --options="low,medium,high"
wp jetonomy-pro custom-fields list --format=json
wp jetonomy-pro custom-fields delete 4

white-label

Set white-label branding for the community frontend.

Subcommand Description
set-logo Replace the community logo
set-colors Set primary and accent brand colors
wp jetonomy-pro white-label set-logo --url=https://example.com/logo.png
wp jetonomy-pro white-label set-colors --primary="#1a73e8" --accent="#fbbc04"

reactions

Manage emoji reactions on posts and replies.

Subcommand Description
list List all reactions on an object
purge Remove all reactions from an object
add Add a reaction from a user
remove Remove a user's reaction

Note: --by is used for the acting user, --on-type and --on-id for the target object.

Flags - add / remove: --on-type=<type> --on-id=<id> --by=<user_id> --emoji=<slug> [--format=<format>]

wp jetonomy-pro reactions add --on-type=post --on-id=12 --by=1 --emoji=thumbsup
wp jetonomy-pro reactions add --on-type=reply --on-id=45 --by=2 --emoji=heart
wp jetonomy-pro reactions list --on-type=post --on-id=12
wp jetonomy-pro reactions purge --on-type=post --on-id=12

ai

Inspect and test the AI moderation / summarization provider.

Subcommand Description
test-provider <provider> Send a test prompt to a named provider
clear-cache Purge the AI response cache
export-usage Export AI API usage records
status Show current provider health
list-providers List all configured AI providers
provider-status <provider> Show status for a specific provider

Flags - test-provider: [--prompt=<text>] [--format=<format>]

wp jetonomy-pro ai status
wp jetonomy-pro ai list-providers --format=json
wp jetonomy-pro ai test-provider anthropic --prompt="Say hi"
wp jetonomy-pro ai clear-cache
wp jetonomy-pro ai export-usage --format=json

advanced-moderation

Inspect and test automated moderation rules.

Subcommand Description
list-rules List all active moderation rules
test-rule <id> Test a rule against sample content
wp jetonomy-pro advanced-moderation list-rules
wp jetonomy-pro advanced-moderation list-rules --format=json
wp jetonomy-pro advanced-moderation test-rule 3

custom-badges

Award and inspect custom reputation badges.

Subcommand Description
list List all badge definitions
award Award a badge to a user

Flags - award: --badge=<id> --to=<user_id> [--format=<format>]

wp jetonomy-pro custom-badges list
wp jetonomy-pro custom-badges award --badge=5 --to=42

polls

Create and manage polls attached to posts.

Subcommand Description
list List polls
close <id> Close a poll early
create Create a poll on a post
get <id> Get poll details and current results
vote Cast a vote on a poll option

Note: --by is used for the voter user ID because --user is reserved.

Flags - create: --post=<id> --question=<text> --options=<csv> [--multiple] [--closes-at=<datetime>] [--format=<format>]

Flags - vote: --post=<id> --by=<user_id> and the option index/id.

wp jetonomy-pro polls create --post=12 --question="Favourite colour?" --options="Red,Green,Blue"
wp jetonomy-pro polls create --post=12 --question="Pick many" --options="A,B,C" --multiple
wp jetonomy-pro polls get 4
wp jetonomy-pro polls close 4

email-digest

Manage user email digest preferences and trigger sends.

Subcommand Description
send-now Send a digest immediately for a user
export-digests Export digest records
get-prefs Get a user's digest preferences
set-prefs Update a user's digest preferences

Note: --for is used for the target user ID because --user is reserved.

Flags - get-prefs / send-now: --for=<user_id> [--format=<format>]

Flags - set-prefs: --for=<user_id> [--frequency=<frequency>] [--enabled] [--disabled] [--types=<csv>] [--format=<format>]

Frequency values: daily, weekly, off.

wp jetonomy-pro email-digest get-prefs --for=1
wp jetonomy-pro email-digest set-prefs --for=1 --frequency=weekly --enabled
wp jetonomy-pro email-digest set-prefs --for=1 --frequency=off
wp jetonomy-pro email-digest send-now --for=1

web-push

Manage browser push subscriptions and send push notifications.

Subcommand Description
send Send a push notification to a user
list-subscriptions List a user's active push subscriptions
subscribe Register a push subscription for a user
unsubscribe Remove a push subscription

Note: --for is used for the subscriber user ID, --to for the notification recipient.

Flags - subscribe: --for=<user_id> --endpoint=<url> --p256dh=<key> --auth=<key> [--format=<format>]

Flags - unsubscribe: --for=<user_id> --endpoint=<url> [--format=<format>]

wp jetonomy-pro web-push list-subscriptions --for=1
wp jetonomy-pro web-push send --to=1 --title="New reply" --body="Alice replied to your post"

analytics

Export and report on community analytics.

Subcommand Description
export Export raw analytics data
report Print a formatted summary report
overview High-level stats for a date range
top-spaces Rank spaces by activity for a period

Flags - overview: [--range=<range>] [--start=<date>] [--end=<date>] [--format=<format>]

Range values: 7d, 30d, 90d, custom. When using custom, provide --start=YYYY-MM-DD and --end=YYYY-MM-DD.

Flags - top-spaces: [--range=<range>] [--limit=<n>] [--format=<format>]

wp jetonomy-pro analytics overview --range=7d
wp jetonomy-pro analytics overview --range=custom --start=2026-03-01 --end=2026-03-15
wp jetonomy-pro analytics top-spaces --range=30d --limit=5
wp jetonomy-pro analytics export --range=30d --format=csv

seo-pro

Generate SEO sitemaps for community content.

Subcommand Description
generate-sitemaps Rebuild all Jetonomy sitemaps
wp jetonomy-pro seo-pro generate-sitemaps

webhooks

Manage outbound webhook endpoints.

Subcommand Description
list List all registered webhooks
test <id> Send a test ping to a webhook
retry <id> Retry failed deliveries for a webhook
create Register a new webhook
update <id> Update webhook settings

Note: --target-url is used instead of --url because WP-CLI reserves --url globally for multisite routing.

Flags - create: --target-url=<url> --events=<csv> [--name=<text>] [--secret=<text>] [--description=<text>] [--disabled] [--format=<format>]

Secret is auto-generated if omitted. Events are a comma-separated list of event slugs such as post.created, reply.created, user.registered.

Flags - update: [--target-url=<url>] [--events=<csv>] [--name=<text>] [--description=<text>] [--format=<format>]

wp jetonomy-pro webhooks list
wp jetonomy-pro webhooks create --target-url=https://example.com/hook --events=post.created,reply.created
wp jetonomy-pro webhooks create --target-url=https://example.com/hook --events=user.registered --disabled
wp jetonomy-pro webhooks test 3
wp jetonomy-pro webhooks retry 3

messaging

Manage private message conversations.

Subcommand Description
export-conversations Export conversation records for a user
purge-old Delete conversations older than a given age
create-conversation Start a new private conversation
send Send a message in an existing conversation

Note: --by is the conversation initiator, --with is a comma-separated list of participant IDs, --from is the message sender.

Flags - create-conversation: --by=<user_id> --with=<csv> [--title=<text>] [--type=<type>] [--message=<text>] [--format=<format>]

Flags - send: --conversation=<id> --from=<user_id> --content=<text> [--format=<format>]

wp jetonomy-pro messaging create-conversation --by=1 --with=3,4
wp jetonomy-pro messaging create-conversation --by=1 --with=2 --message="Hi there"
wp jetonomy-pro messaging send --conversation=12 --from=1 --content="hello"
wp jetonomy-pro messaging export-conversations --by=1

reply-by-email

Configure and test inbound reply-by-email processing.

Subcommand Description
configure Set SMTP / IMAP connection settings
test-smtp Send a test email to verify outbound settings
wp jetonomy-pro reply-by-email configure --host=mail.example.com --user=inbox@example.com
wp jetonomy-pro reply-by-email test-smtp

Testing and QA Commands

These commands run the built-in quality assurance suites against a live WordPress + Jetonomy install. Run them before every release to catch regressions early.

qa-actions (free)

Runs all four smoke-test phases in sequence and reports a pass/fail total. Takes no arguments.

Phase What it covers
Phase 1 REST round-trip tests (creates, reads, deletes via the REST API)
Phase 2 Model unit tests (direct model-layer assertions)
Phase 3 Pro extension tests (skipped automatically on free-only installs)
Phase 4 Journey smoke tests (C1-C12 - full end-to-end user journeys)
wp jetonomy qa-actions

Expected output when all tests pass:

--- Jetonomy Action Tests ---

Phase 1: REST Round-Trip Tests
...
Phase 4: Journey Smoke Tests (C1-C12)
...
  REST Tests:     42/42
  Model Tests:    58/58
  Pro Tests:      64/64
  Journey Tests:  46/46
  Total: 210/210 PASS

scenario run (free)

Run a named end-to-end scenario against the live database. Use --cleanup to roll back all data the scenario creates.

# List available scenarios first
wp jetonomy scenario list

# Run a scenario and keep the data (useful for manual inspection after)
wp jetonomy scenario run basic-forum-flow

# Run and clean up all created data
wp jetonomy scenario run notification-delivery-sweep --cleanup
wp jetonomy scenario run moderation-lifecycle --cleanup

Scenarios are PHP classes under includes/cli/scenarios/ and can be run directly by PHPUnit via composer test:combo if you prefer not to use a live database.

Expose your community to AI agents and automation through the WordPress Abilities API - 19 free abilities plus 20 more with Pro, each enforcing the same permissions as the REST API.

What You Will Learn

  • What the Abilities API is and when to use it instead of REST
  • Every ability Jetonomy and Jetonomy Pro register
  • How permissions work (they mirror REST exactly)
  • How to execute an ability from PHP or WP-CLI

What Is the Abilities API?

The WordPress Abilities API is a machine-readable registry of "things a site can do", each with a name, description, JSON-Schema input/output contracts, and a permission callback. AI agents, MCP servers, and automation tools discover abilities at runtime and call them without knowing your REST routes.

Where the API comes from: the Abilities API ships in WordPress core from version 7.0 onward. On earlier versions (6.7-6.9) it is available as the standalone WordPress Abilities API feature plugin - install and activate that plugin and the API works the same way.

Jetonomy registers its abilities only when the API is actually present. It hooks the API's own wp_abilities_api_init / wp_abilities_api_categories_init actions, which fire whether the API came from core (7.0+) or from the feature plugin on an older release. If neither core nor the plugin provides the API, those hooks never fire and Jetonomy simply does not register abilities - no errors, no changed behaviour, and the REST API remains the way to drive the community.

Free Abilities

All free abilities live under the jetonomy/ namespace, grouped into categories (jetonomy-content, jetonomy-spaces, jetonomy-users, jetonomy-moderation, jetonomy-search).

Ability What it does
jetonomy/list-spaces, jetonomy/get-space Browse spaces
jetonomy/create-space, jetonomy/join-space, jetonomy/list-space-members Space membership
jetonomy/list-posts, jetonomy/get-post, jetonomy/create-post Topics
jetonomy/list-replies, jetonomy/create-reply Replies
jetonomy/vote Vote on posts and replies
jetonomy/search Full-text search
jetonomy/get-activity, jetonomy/get-user-profile Member data
jetonomy/list-notifications, jetonomy/mark-notifications-read Notifications
jetonomy/flag-content, jetonomy/list-flags, jetonomy/moderate-content Moderation

Pro Abilities

Pro adds 20 abilities under jetonomy-pro/ covering its extensions:

Area Abilities
Messaging list-conversations, get-conversation, send-message
Polls create-poll, vote-poll, get-poll-results
Reactions get-reactions, add-reaction
Badges list-badges, get-user-badges, award-badge
Custom fields list-custom-fields, set-custom-field
Analytics get-analytics, export-analytics
Webhooks list-webhooks, create-webhook, delete-webhook
Moderation rules list-moderation-rules, create-moderation-rule

Permissions Mirror REST

Every ability executes through the same REST stack that powers the frontend (improved in 1.5.0 - abilities previously used a separate code path). That means the permission model is identical: an agent acting as a subscriber cannot read someone else's conversation, list webhooks, or award badges, and gets the same jetonomy_forbidden / rest_forbidden errors the REST API returns. There is no separate "abilities permission system" to audit.

Executing an Ability

From PHP:

$ability = wp_get_ability( 'jetonomy/create-post' );
if ( $ability ) {
    $result = $ability->execute( [
        'space_id' => 8,
        'title'    => 'Posted by an agent',
        'content'  => 'Hello from the Abilities API.',
    ] );
}

Abilities that take no input are executed with no arguments: wp_get_ability( 'jetonomy/list-spaces' )->execute().

From WP-CLI (WordPress 7.0+):

wp ability run jetonomy/search --input='{"q":"onboarding"}'

What's Next?

The same operations are available over plain HTTP if you prefer direct REST calls.

REST API →

Add a custom tab to a member's profile page using the jetonomy_profile_tabs filter - no template override needed. The tab appears next to Posts, Replies, and Votes; clicking it loads a separate route that you register alongside the tab.


How Profile Tabs Work

When Jetonomy renders a profile page it builds an ordered map of tabs, then passes it through jetonomy_profile_tabs before rendering the tab bar. Each entry in the map is a slug keyed to a label and URL. The posts tab is active when no sub-tab is in the URL; all other tabs are matched by slug against the current URL.

Rendering rule: every non-empty entry with a label and url key is rendered. There is no minimum count - even a single tab will render. To remove a built-in tab, unset its slug from the map before returning it.

Content rule: a custom tab is just a link. Clicking it navigates to the URL you specify. Content for that URL must come from a separate route and template registered via jetonomy_template_map (#03-template-overrides-jtnmy)).


The jetonomy_profile_tabs Filter

apply_filters( 'jetonomy_profile_tabs', $tabs, $user, $is_own )

Parameters

Parameter Type Description
$tabs array Ordered map: slug => ['label' => string, 'url' => string]. Built-ins: posts, replies, votes, bookmarks (own only), drafts (own only).
$user WP_User The profile owner.
$is_own bool true when the viewing user is the profile owner.

Return: array - the modified tab map.

Source: templates/views/user-profile.php

Built-in slugs and active-state rule: The posts tab is active when the URL is the bare profile URL (no sub-tab segment). All other tabs are active when their slug appears as the jetonomy_tab query var. Setting a tab's URL to your own route means Jetonomy's active-tab logic will not apply to it - your template is responsible for highlighting the tab.


Step 1: Register a Template for the Tab Content

Register a new route via jetonomy_template_map and point it to a PHP template in your plugin or theme. The template receives the user login from get_query_var( 'jetonomy_slug' ).

add_filter( 'jetonomy_template_map', function( array $map ): array {
    // Absolute path bypasses the theme-override check - correct for plugin templates.
    $map['profile-portfolio'] = MY_PLUGIN_DIR . 'templates/profile-portfolio.php';
    return $map;
} );

A minimal template for the route:

<?php
// my-plugin/templates/profile-portfolio.php

$login = get_query_var( 'jetonomy_slug' );
$user  = get_user_by( 'login', $login );

if ( ! $user ) {
    wp_redirect( home_url( '/' ) );
    exit;
}
?>
<div class="jt-app">
    <?php \Jetonomy\Template_Loader::partial( 'header' ); ?>
    <div class="jt-container">
        <h2><?php echo esc_html( $user->display_name ); ?> - Portfolio</h2>
        <?php
        // Your custom content here.
        ?>
    </div>
</div>

Step 2: Register the Rewrite Rule

Teach Jetonomy's router about the new URL pattern. The slug captured in $matches[1] is passed as jetonomy_slug so the template can look up the user.

add_action( 'init', function() {
    $settings  = get_option( 'jetonomy_settings', [] );
    $base      = $settings['base_slug'] ?? 'community';

    add_rewrite_rule(
        '^' . preg_quote( $base, '^' ) . '/u/([^/]+)/portfolio/?$',
        'index.php?jetonomy_route=profile-portfolio&jetonomy_slug=$matches[1]',
        'top'
    );
} );

After adding this code, flush permalinks once: Settings → Permalinks → Save Changes, or via WP-CLI:

wp --path="/path/to/wordpress" rewrite flush

Step 3: Add the Tab

Hook into jetonomy_profile_tabs and append your tab. Build the URL from the $user parameter so you never need to look up the user again.

add_filter( 'jetonomy_profile_tabs', function( array $tabs, WP_User $user, bool $is_own ): array {
    $settings = get_option( 'jetonomy_settings', [] );
    $base     = $settings['base_slug'] ?? 'community';

    $tabs['portfolio'] = [
        'label' => __( 'Portfolio', 'my-plugin' ),
        'url'   => home_url( "/{$base}/u/{$user->user_login}/portfolio/" ),
    ];

    return $tabs;
}, 10, 3 );

Complete Example

The following block can be placed in your theme's functions.php or in a site-specific mu-plugin. Replace MY_PLUGIN_DIR with the correct path if you are not working inside a plugin.

/**
 * Portfolio tab on member profiles.
 *
 * 1. Registers a /community/u/{login}/portfolio/ URL.
 * 2. Loads a custom template for that URL.
 * 3. Adds a "Portfolio" tab to the profile tab bar.
 */

// Step 1 - template.
add_filter( 'jetonomy_template_map', function( array $map ): array {
    // Point to a template file in your plugin or theme.
    $map['profile-portfolio'] = get_stylesheet_directory() . '/jetonomy/views/profile-portfolio.php';
    return $map;
} );

// Step 2 - rewrite rule.
add_action( 'init', function() {
    $settings = get_option( 'jetonomy_settings', [] );
    $base     = $settings['base_slug'] ?? 'community';

    add_rewrite_rule(
        '^' . preg_quote( $base, '^' ) . '/u/([^/]+)/portfolio/?$',
        'index.php?jetonomy_route=profile-portfolio&jetonomy_slug=$matches[1]',
        'top'
    );
} );

// Step 3 - tab.
add_filter( 'jetonomy_profile_tabs', function( array $tabs, WP_User $user, bool $is_own ): array {
    $settings = get_option( 'jetonomy_settings', [] );
    $base     = $settings['base_slug'] ?? 'community';

    $tabs['portfolio'] = [
        'label' => __( 'Portfolio', 'my-plugin' ),
        'url'   => home_url( "/{$base}/u/{$user->user_login}/portfolio/" ),
    ];

    return $tabs;
}, 10, 3 );

Removing or Reordering Built-in Tabs

Remove a tab: unset it from the map before returning.

add_filter( 'jetonomy_profile_tabs', function( array $tabs, WP_User $user, bool $is_own ): array {
    // Remove the Votes tab from all profiles.
    unset( $tabs['votes'] );

    // Remove Bookmarks from other people's profiles (it is already hidden
    // by the template for non-owners, but removing it here prevents the
    // URL from loading via direct navigation).
    if ( ! $is_own ) {
        unset( $tabs['bookmarks'] );
    }

    return $tabs;
}, 10, 3 );

Reorder tabs: PHP arrays preserve insertion order, so rebuild the map in the sequence you want.

add_filter( 'jetonomy_profile_tabs', function( array $tabs, WP_User $user, bool $is_own ): array {
    // Move Replies before Posts.
    $replies = $tabs['replies'] ?? null;
    if ( $replies ) {
        unset( $tabs['replies'] );
        // Re-insert at the front using array union.
        $tabs = [ 'replies' => $replies ] + $tabs;
    }
    return $tabs;
}, 10, 3 );

Relabel a tab:

add_filter( 'jetonomy_profile_tabs', function( array $tabs ): array {
    if ( isset( $tabs['posts'] ) ) {
        $tabs['posts']['label'] = __( 'Topics', 'my-plugin' );
    }
    return $tabs;
}, 10, 3 );

Notes

  • Flush permalinks after changing any rewrite rule. In a plugin, call flush_rewrite_rules() on activation and deactivation hooks - never on every request.
  • The jetonomy_slug and jetonomy_route query vars are already registered by Jetonomy's router; you do not need to add them to query_vars.
  • If your tab must only appear for logged-in users, check is_user_logged_in() inside the filter before appending the tab.
  • See Template Overrides for calling partials (header, breadcrumb) correctly inside your custom template so theme overrides are respected.

Add a custom tab to a space page using the jetonomy_space_tabs filter. The tab appears alongside Discussions (or Questions / Ideas / Posts), Roadmap, and Members; clicking it loads a route you register separately.


How Space Tabs Work

When Jetonomy renders a space page it assembles an ordered tab map, then passes it through jetonomy_space_tabs before rendering the nav. Two details determine whether and how the nav renders:

The >1-tab rule: the <nav class="jt-space-tabs"> element is only rendered when the filtered map contains more than one tab. If your filter results in exactly one tab, no nav appears. Adding a custom tab to the default single-tab view (a non-ideas, members-hidden space) creates two tabs and triggers nav rendering.

The active flag: each tab entry can carry an 'active' => bool key. Set this to true on the tab that represents the current page. Jetonomy reads 'active' to add the on CSS class and aria-current="page" attribute. The built-in primary tab always starts as active => true; when your custom route renders, set active => false on primary and active => true on your tab (see the template example below).


The jetonomy_space_tabs Filter

apply_filters( 'jetonomy_space_tabs', $tabs, $space, $show_members )

Parameters

Parameter Type Description
$tabs array Ordered map: slug => ['label' => string, 'url' => string, 'active' => bool]. Built-ins: primary (always), roadmap (ideas spaces), members (logged-in users).
$space object The space being viewed (slug, type, name, etc.).
$show_members bool Whether the Members tab is visible (is_user_logged_in()).

Return: array - the modified tab map. Entries missing label or url are skipped.

Source: templates/views/space.php


Step 1: Register a Template for the Tab Content

Register a new route via jetonomy_template_map. The template receives the space slug from get_query_var( 'jetonomy_slug' ).

add_filter( 'jetonomy_template_map', function( array $map ): array {
    $map['space-analytics'] = MY_PLUGIN_DIR . 'templates/space-analytics.php';
    return $map;
} );

A minimal template for the route. It sets the active flag correctly on the tab map via the same filter - because the filter runs again when this template is loaded under the space-analytics route:

<?php
// my-plugin/templates/space-analytics.php

$slug  = get_query_var( 'jetonomy_slug' );
$space = \Jetonomy\Models\Space::find_by_slug( $slug );

if ( ! $space ) {
    wp_redirect( home_url( '/' ) );
    exit;
}
?>
<div class="jt-app">
    <?php \Jetonomy\Template_Loader::partial( 'header' ); ?>
    <div class="jt-container">
        <?php
        // Your custom content here.
        // The tab bar is rendered by the partials/header - no need to
        // re-emit it here. Your filter below handles the active flag.
        ?>
        <h2><?php echo esc_html( $space->name ); ?> - Analytics</h2>
    </div>
</div>

Step 2: Register the Rewrite Rule

add_action( 'init', function() {
    $settings = get_option( 'jetonomy_settings', [] );
    $base     = $settings['base_slug'] ?? 'community';

    add_rewrite_rule(
        '^' . preg_quote( $base, '^' ) . '/s/([^/]+)/analytics/?$',
        'index.php?jetonomy_route=space-analytics&jetonomy_slug=$matches[1]',
        'top'
    );
} );

Flush permalinks once after adding: Settings → Permalinks → Save Changes or wp rewrite flush.


Step 3: Add the Tab

Append the tab inside the filter. When running under the space-analytics route, also flip the active flag so the correct tab is highlighted.

add_filter( 'jetonomy_space_tabs', function( array $tabs, object $space, bool $show_members ): array {
    $settings      = get_option( 'jetonomy_settings', [] );
    $base          = $settings['base_slug'] ?? 'community';
    $analytics_url = home_url( "/{$base}/s/{$space->slug}/analytics/" );

    // Detect whether we are currently on the analytics page.
    $is_active = ( 'space-analytics' === get_query_var( 'jetonomy_route' ) );

    // When analytics is the active page, un-mark the primary tab.
    if ( $is_active && isset( $tabs['primary'] ) ) {
        $tabs['primary']['active'] = false;
    }

    $tabs['analytics'] = [
        'label'  => __( 'Analytics', 'my-plugin' ),
        'url'    => $analytics_url,
        'active' => $is_active,
    ];

    return $tabs;
}, 10, 3 );

Complete Example

/**
 * Analytics tab on space pages.
 *
 * 1. Registers /community/s/{slug}/analytics/.
 * 2. Loads a custom template for that URL.
 * 3. Adds an "Analytics" tab to the space tab bar.
 * 4. Marks the tab active when the analytics route is loaded.
 */

// Step 1 - template.
add_filter( 'jetonomy_template_map', function( array $map ): array {
    $map['space-analytics'] = get_stylesheet_directory() . '/jetonomy/views/space-analytics.php';
    return $map;
} );

// Step 2 - rewrite rule.
add_action( 'init', function() {
    $settings = get_option( 'jetonomy_settings', [] );
    $base     = $settings['base_slug'] ?? 'community';

    add_rewrite_rule(
        '^' . preg_quote( $base, '^' ) . '/s/([^/]+)/analytics/?$',
        'index.php?jetonomy_route=space-analytics&jetonomy_slug=$matches[1]',
        'top'
    );
} );

// Step 3 - tab (handles both the space view and the analytics route).
add_filter( 'jetonomy_space_tabs', function( array $tabs, object $space, bool $show_members ): array {
    $settings      = get_option( 'jetonomy_settings', [] );
    $base          = $settings['base_slug'] ?? 'community';
    $analytics_url = home_url( "/{$base}/s/{$space->slug}/analytics/" );
    $is_active     = ( 'space-analytics' === get_query_var( 'jetonomy_route' ) );

    if ( $is_active && isset( $tabs['primary'] ) ) {
        $tabs['primary']['active'] = false;
    }

    $tabs['analytics'] = [
        'label'  => __( 'Analytics', 'my-plugin' ),
        'url'    => $analytics_url,
        'active' => $is_active,
    ];

    return $tabs;
}, 10, 3 );

Removing or Reordering Built-in Tabs

Remove a tab:

add_filter( 'jetonomy_space_tabs', function( array $tabs ): array {
    // Hide the Members tab entirely.
    unset( $tabs['members'] );
    return $tabs;
}, 10, 3 );

Relabel a tab:

add_filter( 'jetonomy_space_tabs', function( array $tabs ): array {
    if ( isset( $tabs['roadmap'] ) ) {
        $tabs['roadmap']['label'] = __( 'Planned Features', 'my-plugin' );
    }
    return $tabs;
}, 10, 3 );

Reorder tabs (rebuild map):

add_filter( 'jetonomy_space_tabs', function( array $tabs ): array {
    // Place Members before Roadmap.
    $members = $tabs['members'] ?? null;
    $roadmap = $tabs['roadmap'] ?? null;

    if ( $members && $roadmap ) {
        unset( $tabs['members'], $tabs['roadmap'] );
        $tabs['members'] = $members;
        $tabs['roadmap'] = $roadmap;
    }

    return $tabs;
}, 10, 3 );

Notes

  • The >1-tab rule is evaluated after your filter runs. If your filter removes tabs and leaves only one, the nav will be hidden - this is by design.
  • The active key is optional; entries without it are treated as inactive. You only need to set it on the tab representing the current page.
  • The jetonomy_slug and jetonomy_route query vars are already registered by Jetonomy's router; you do not need to register them again.
  • Flush permalinks whenever you add or modify a rewrite rule. Use activation/deactivation hooks in a plugin - never call flush_rewrite_rules() on every request.
  • See Template Overrides for the full jetonomy_template_map contract and how to call partials correctly from custom templates.

Add a link or button to Jetonomy's community top nav using the jetonomy_header_nav_items action - no template override needed.


How the Community Nav Works

The community header nav (partials/header.php) renders a row of icon-and-label links: Home, Leaderboard, Profile, Moderation. It closes with do_action( 'jetonomy_header_nav_items' ) after the built-in items, so anything you echo from that hook appears at the right end of the nav rail.

The nav is rendered on every Jetonomy page. The active state on built-in links is set by comparing the current $current_route (from get_query_var('jetonomy_route')) to a hard-coded route name. For custom items, manage the active class yourself.

Source: templates/partials/header.php


The jetonomy_header_nav_items Action

do_action( 'jetonomy_header_nav_items' )

Parameters: none.

Where it fires: inside .jt-community-nav-links, after all built-in nav links and before the actions rail (notifications bell, avatar).


add_action( 'jetonomy_header_nav_items', function() {
    echo '<a href="' . esc_url( home_url( '/events/' ) ) . '" class="jt-nav-link" title="' . esc_attr__( 'Events', 'my-plugin' ) . '">';
    echo '<span class="jt-nav-label">' . esc_html__( 'Events', 'my-plugin' ) . '</span>';
    echo '</a>';
} );

The built-in nav links use jetonomy_echo_icon() for their SVG icons. You can use the same function if you want a consistent icon style, or pass your own SVG. The example below adds a "Help" link that highlights when the user is on your custom help route.

add_action( 'jetonomy_header_nav_items', function() {
    $current_route = get_query_var( 'jetonomy_route' );
    $is_active     = ( 'help' === $current_route );

    // Build the URL from the Jetonomy base slug so it adapts if the site
    // owner has customized the community path.
    $settings  = get_option( 'jetonomy_settings', [] );
    $base      = $settings['base_slug'] ?? 'community';
    $help_url  = home_url( "/{$base}/help/" );

    $classes = 'jt-nav-link' . ( $is_active ? ' active' : '' );

    echo '<a href="' . esc_url( $help_url ) . '" class="' . esc_attr( $classes ) . '" title="' . esc_attr__( 'Help', 'my-plugin' ) . '">';

    // Use Jetonomy's icon helper if available, or inline SVG as fallback.
    if ( function_exists( 'jetonomy_echo_icon' ) ) {
        jetonomy_echo_icon( 'help-circle', 18 );
    } else {
        // Minimal inline SVG fallback.
        echo '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>';
    }

    echo '<span class="jt-nav-label">' . esc_html__( 'Help', 'my-plugin' ) . '</span>';
    echo '</a>';
} );

Adding a Nav Item Only for Specific Roles

add_action( 'jetonomy_header_nav_items', function() {
    // Only show the moderation shortcut to users who can moderate.
    if ( ! current_user_can( 'jetonomy_moderate' ) ) {
        return;
    }

    $settings = get_option( 'jetonomy_settings', [] );
    $base     = $settings['base_slug'] ?? 'community';

    echo '<a href="' . esc_url( home_url( "/{$base}/mod/queue/" ) ) . '" class="jt-nav-link" title="' . esc_attr__( 'Queue', 'my-plugin' ) . '">';
    echo '<span class="jt-nav-label">' . esc_html__( 'Queue', 'my-plugin' ) . '</span>';
    echo '</a>';
} );

Hiding the Built-in Nav

If you need to suppress the entire built-in community nav (for example, when a bridge plugin provides its own navigation), use the jetonomy_show_community_nav filter instead of working around the action:

// Hide the community nav bar entirely (documented in 02-hooks-reference.md).
add_filter( 'jetonomy_show_community_nav', '__return_false' );

Notes

  • Echo valid anchor elements only. The hook fires inside a <div> - block-level elements will break the nav layout. Use <a> tags styled with jt-nav-link to match the built-in items, or provide your own class.
  • Use esc_url(), esc_attr(), and esc_html() on all output. Never echo raw variable values.
  • The hook fires on every Jetonomy page including admin-facing moderation views, so conditional checks (logged-in state, capability, route) prevent items appearing in the wrong context.
  • If you want to remove a built-in nav item rather than add a new one, override partials/header.php in your theme. See Template Overrides.

Inject badges, buttons, counts, and metadata into post cards, reply cards, space cards, and member rows using purpose-built hooks - no template override required for most use cases.


Post Cards

Post cards appear in the space topic listing, the home grid, and in search results. The single-post header reuses the same jetonomy_post_card_after_badges hook so a badge added here appears consistently in both surfaces.

jetonomy_post_card_after_badges

Fires right after the built-in status badges (Sticky, Private, Pinned) inside the card. Use this to append your own badge or marker. This hook fires in both the listing card and the single-post header.

do_action( 'jetonomy_post_card_after_badges', $post, $space )

Parameters

Parameter Type Description
$post object The post row being rendered.
$space object|null The post's space, if loaded.

Source: templates/partials/post-card.php, templates/views/single-post.php

Example: Featured badge

add_action( 'jetonomy_post_card_after_badges', function( object $post, $space ) {
    if ( ! get_post_meta( $post->id, '_my_featured', true ) ) {
        return;
    }
    echo '<span class="my-badge my-badge--featured">' . esc_html__( 'Featured', 'my-plugin' ) . '</span>';
}, 10, 2 );

Example: Space-type label (only on the home grid where $space may vary)

add_action( 'jetonomy_post_card_after_badges', function( object $post, $space ) {
    if ( ! $space || 'qa' !== ( $space->type ?? '' ) ) {
        return;
    }
    echo '<span class="my-badge my-badge--qa">' . esc_html__( 'Q&A', 'my-plugin' ) . '</span>';
}, 10, 2 );

jetonomy_post_actions

Fires inside the single-post action toolbar, alongside the built-in actions (vote, bookmark, share). Use this to inject a custom action button visible on a single post view.

do_action( 'jetonomy_post_actions', $post )

Parameters

Parameter Type Description
$post object The current post being viewed.

Source: templates/views/single-post.php

Example: "Translate" button

add_action( 'jetonomy_post_actions', function( object $post ) {
    if ( ! is_user_logged_in() ) {
        return;
    }
    $nonce = wp_create_nonce( 'my_translate_' . $post->id );
    printf(
        '<button class="jt-action-btn my-translate-btn" data-post-id="%d" data-nonce="%s" aria-label="%s">%s</button>',
        (int) $post->id,
        esc_attr( $nonce ),
        esc_attr__( 'Translate this post', 'my-plugin' ),
        esc_html__( 'Translate', 'my-plugin' )
    );
} );

jetonomy_after_post_content (filter)

Filters the HTML rendered directly after the post body in the single-post view. Return a non-empty string to inject content between the post body and the action row.

apply_filters( 'jetonomy_after_post_content', $html, $post )

Parameters

Parameter Type Description
$html string HTML to render after the post content (empty by default).
$post \Jetonomy\Models\Post The current post object.

Return: string

Source: templates/views/single-post.php

Example: Related posts block

add_filter( 'jetonomy_after_post_content', function( string $html, $post ): string {
    $related = my_plugin_get_related( (int) $post->id );
    if ( empty( $related ) ) {
        return $html;
    }

    ob_start();
    echo '<div class="my-related-posts">';
    echo '<strong>' . esc_html__( 'Related discussions', 'my-plugin' ) . '</strong>';
    echo '<ul>';
    foreach ( $related as $r ) {
        printf(
            '<li><a href="%s">%s</a></li>',
            esc_url( $r['url'] ),
            esc_html( $r['title'] )
        );
    }
    echo '</ul></div>';
    $html .= ob_get_clean();

    return $html;
}, 10, 2 );

jetonomy_after_post_article

Fires after the main post <article> element and before the replies section. Ideal for ads, CTAs, or related-content blocks placed between the topic body and the reply list.

do_action( 'jetonomy_after_post_article', $post )

Parameters

Parameter Type Description
$post object The current post object.

Source: templates/views/single-post.php

Example: Ad placement

add_action( 'jetonomy_after_post_article', function( object $post ) {
    echo do_shortcode( '[my_ad zone="community_post_bottom"]' );
} );

Reply Cards

Reply cards appear in the single-post reply list. The hook fires for every top-level reply in both the "opening" and "latest" batches.

jetonomy_reply_actions

Fires inside the reply card, within the reply action row, alongside the built-in reply actions (upvote, accept, flag).

do_action( 'jetonomy_reply_actions', $reply )

Parameters

Parameter Type Description
$reply object The reply being rendered.

Source: templates/partials/reply-card.php

Example: "Mark as helpful" button

add_action( 'jetonomy_reply_actions', function( object $reply ) {
    if ( ! is_user_logged_in() ) {
        return;
    }
    $count   = (int) get_user_meta( $reply->id, '_helpful_count', true );
    $nonce   = wp_create_nonce( 'my_helpful_' . $reply->id );
    printf(
        '<button class="jt-action-btn my-helpful-btn" data-reply-id="%d" data-nonce="%s" aria-pressed="false">%s <span class="my-helpful-count">%d</span></button>',
        (int) $reply->id,
        esc_attr( $nonce ),
        esc_html__( 'Helpful', 'my-plugin' ),
        $count
    );
} );

Space Cards

Space cards appear on the community home grid (/community/) and the category page (/community/category/{slug}/). The hook fires outside the card's <a> wrapper, so interactive elements (buttons, forms) are valid HTML here.

jetonomy_space_card_after

Fires after each space card, outside its <a> wrapper.

do_action( 'jetonomy_space_card_after', $space )

Parameters

Parameter Type Description
$space object The space being rendered.

Source: templates/views/home.php, templates/views/category.php

Example: "New" badge on recently created spaces

add_action( 'jetonomy_space_card_after', function( object $space ) {
    $created = strtotime( $space->created_at ?? '' );
    if ( ! $created || ( time() - $created ) > WEEK_IN_SECONDS ) {
        return;
    }
    echo '<span class="my-space-badge my-space-badge--new">' . esc_html__( 'New', 'my-plugin' ) . '</span>';
} );

Example: Join button outside the card link

Since this hook fires outside the <a> wrapper, you can safely render a button here without nesting interactive elements inside the link.

add_action( 'jetonomy_space_card_after', function( object $space ) {
    if ( ! is_user_logged_in() ) {
        return;
    }
    // Only show the button if the user is not already a member.
    if ( \Jetonomy\Models\SpaceMember::is_member( get_current_user_id(), (int) $space->id ) ) {
        return;
    }
    printf(
        '<button class="my-space-join-btn" data-space-id="%d">%s</button>',
        (int) $space->id,
        esc_html__( 'Join', 'my-plugin' )
    );
} );

Member Cards (Space Members List)

Member rows appear on the space members page (/community/s/{slug}/members/). The hook fires after each row's closing element, giving you a slot for per-member extras like badges, direct-message links, or moderator actions.

jetonomy_member_card_after

Fires after each member row in the space members list.

do_action( 'jetonomy_member_card_after', $member, $space )

Parameters

Parameter Type Description
$member object The space membership row. Properties: user_id (int), role (string), joined_at (string datetime).
$space object The space being viewed.

Source: templates/views/space-members.php

Example: Badge count for each member

add_action( 'jetonomy_member_card_after', function( object $member, object $space ) {
    $count = my_badges_count_for_user( (int) $member->user_id );
    if ( $count < 1 ) {
        return;
    }
    printf(
        '<span class="my-member-badge-count" title="%s">%d %s</span>',
        esc_attr__( 'Badges earned', 'my-plugin' ),
        (int) $count,
        esc_html( _n( 'badge', 'badges', $count, 'my-plugin' ) )
    );
}, 10, 2 );

Example: Message button (only for Pro users with messaging)

add_action( 'jetonomy_member_card_after', function( object $member, object $space ) {
    $current_user_id = get_current_user_id();
    // Do not show the button to guests or to the member themselves.
    if ( ! $current_user_id || (int) $member->user_id === $current_user_id ) {
        return;
    }
    $settings = get_option( 'jetonomy_settings', [] );
    $base     = $settings['base_slug'] ?? 'community';
    printf(
        '<a href="%s" class="jt-btn jt-btn--ghost jt-btn--sm my-dm-btn">%s</a>',
        esc_url( home_url( "/{$base}/messages/?to={$member->user_id}" ) ),
        esc_html__( 'Message', 'my-plugin' )
    );
}, 10, 2 );

Styling Custom Injected Content

Use Jetonomy's CSS tokens so your injected elements adapt to the active theme, dark mode, and RTL layout automatically.

/* your-plugin/assets/css/my-plugin.css */

/* Badge appended via jetonomy_post_card_after_badges */
.my-badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 0.7rem;
    font-weight: 600;
    padding: 2px 6px;
    border-radius: var(--jt-radius-sm);
    background: var(--jt-accent-light);
    color: var(--jt-accent);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.my-badge--featured {
    background: var(--jt-warn-light);
    color: var(--jt-warn);
}

/* Button appended via jetonomy_member_card_after */
.my-dm-btn {
    margin-inline-start: auto; /* RTL-safe push to the trailing edge */
}

Available tokens are listed in the plugin's CLAUDE.md CSS Token Rules section. The key categories are --jt-accent-*, --jt-text-*, --jt-bg-*, --jt-radius-*, and --jt-border. Never use hardcoded hex or px values in styles that ship alongside these hooks.


Notes

  • jetonomy_post_card_after_badges fires in both the listing card and the single-post header. Test both surfaces when you add a badge.
  • jetonomy_space_card_after fires outside the <a> wrapper - this is intentional so buttons and forms are valid HTML. Don't wrap the emitted content in another <a>.
  • jetonomy_member_card_after fires inside the members list grid, not inside any wrapper link, so interactive elements are valid.
  • For heavier customizations that require restructuring the card layout, use Template Overrides instead.
  • See Hooks Reference for the full list of hooks, including sidebar, reply, and between-replies slots.

Jetonomy uses a single set of CSS custom properties - the --jt-* token system - to control every colour, radius, and font across the community UI. All tokens are declared in one place, adopt the active theme's own brand colour automatically, and can be overridden from multiple layers: the admin accent/palette fields, the jetonomy_dynamic_css filter, or a child-theme stylesheet. This page explains the full layering chain.

Source references:

  • Token definitions: assets/css/jetonomy-tokens.css (:root block, handle jetonomy-tokens)
  • Token consumer (declares none): assets/css/jetonomy.css
  • Dynamic CSS assembly: includes/class-template-loader.php, Template_Loader::render()
  • palette_css() / palette_tokens() methods: includes/class-template-loader.php:785 / :819
  • Host-theme adoption standard: docs/standards/host-theme-color-adoption.md

The five admin palette settings

Under Jetonomy → Settings → Appearance → Color Palette, site owners can set up to five colors. Each maps to a root CSS token:

Setting key (in jetonomy_settings) CSS token written
accent_color --jt-accent
text_color --jt-text
bg_color --jt-bg
bg_subtle_color --jt-bg-subtle
border_color --jt-border

palette_css() emits a single :root { ... } block containing only the colors the owner actually set (includes/class-template-loader.php:785). An empty field means "keep the default", so a site that saves nothing gets zero style change. The default value #0073aa for accent_color is treated as "unset, adopt the theme" and never emitted - this is the accent picker's "not set" sentinel (palette_tokens(), includes/class-template-loader.php:819).

Derived tokens (such as --jt-accent-hover and --jt-text-secondary) are color-mix() expressions over these root tokens, so they recompute automatically when a root token changes. Dark mode is unaffected: the .jt-dark, [data-theme="dark"] block reassigns the same :root tokens on the class the host theme puts on <body>, and every --jt-* consumer inherits from it.


Token catalogue

All --jt-* tokens are declared on :root only in assets/css/jetonomy-tokens.css (never on .jt-app, and never re-declared per component - see the Dark-mode rule below for why). The root tokens adopt the active theme before falling back to a hard-coded default:

/* Root tokens (condensed) */
--jt-font:   var(--font-body, var(--wp--preset--font-family--body, inherit));
--jt-accent: var(--brand, var(--wp--preset--color--primary, #0073aa)); /* full chain below */
--jt-text:   var(--bx-color-fg, var(--text-1, var(--wp--preset--color--contrast, #1a1a1a)));
--jt-bg:     var(--bx-color-bg-elevated, var(--bg, var(--wp--preset--color--base, #ffffff)));
--jt-radius: var(--r-md, var(--wp--custom--border-radius, 8px));

The full --jt-accent adoption chain and the WCAG contrast guard that derives readable button text on top of it are described in the next section.

Category Tokens
Typography --jt-font, --jt-font-heading, --jt-font-mono
Accent --jt-accent, --jt-accent-hover, --jt-accent-light, --jt-accent-muted
Text --jt-text, --jt-text-secondary, --jt-text-tertiary
Background --jt-bg, --jt-bg-subtle, --jt-bg-muted, --jt-bg-hover
Border --jt-border, --jt-border-strong
Semantic --jt-success, --jt-success-light, --jt-warn, --jt-warn-light, --jt-danger, --jt-danger-light
Trust levels --jt-tl0 through --jt-tl5
Badge tiers --jt-badge-bronze, --jt-badge-silver, --jt-badge-gold
Radius --jt-radius, --jt-radius-sm, --jt-radius-lg, --jt-radius-full
Motion --jt-ease, --jt-dur

Host-theme colour adoption (unconditional)

Jetonomy always adopts the active theme's own brand colour. There is no toggle: the --jt-accent chain is declared statically in :root in jetonomy-tokens.css and resolves to the first token the active theme actually defines:

:root {
    --jt-accent: var(--bx-color-accent,           /* BuddyX / BuddyX Pro 5.1+ */
                 var(--reign-colors-theme,        /* Reign 8.0+               */
                 var(--brand,                     /* BuddyNext                */
                 var(--wp--preset--color--primary,/* "primary"-slug themes    */
                 var(--ast-global-color-0,        /* Astra                    */
                 var(--global-palette1,           /* Kadence                  */
                 var(--theme-palette-color-1,     /* Blocksy                  */
                 var(--nv-primary-accent,         /* Neve                     */
                 var(--wp--preset--color--accent, /* GeneratePress + "accent" */
                 #0073aa)))))))));                 /* neutral default          */
    --jt-text: var(--bx-color-fg,
                 var(--text-1,
                   var(--wp--preset--color--contrast, #1a1a1a)));
    --jt-bg:   var(--bx-color-bg-elevated,
                 var(--bg,
                   var(--wp--preset--color--base, #ffffff)));
}

Source: assets/css/jetonomy-tokens.css (the :root block, --jt-accent). The tokens are mutually exclusive per active theme, so ordering only decides precedence for the rare theme that exposes two. Because each link is var(token, fallback), the accent also follows the theme's dark mode for free when the theme flips its own token.

Removed in 1.8.0. The inherit_colors and inherit_fonts settings no longer exist. inherit_colors defaulted to checked and, while on, made palette_tokens() return nothing - so an owner who picked an accent had it silently discarded. Adoption is now unconditional and the accent field is the single override (see below). inherit_fonts emitted --jt-font:inherit, which changed nothing because the --jt-font chain already ends in inherit. Both removals are documented in includes/class-template-loader.php:218.

The accent field is the single override

To pin an exact accent instead of the adopted one, set Settings → Appearance → Accent Color to any value other than the #0073aa default. That emits :root{--jt-accent:<chosen>} inline via Template_Loader::palette_css(), which outranks the static chain on every theme. Leaving it at #0073aa (the "not set" sentinel) keeps adoption active - palette_tokens() deliberately skips that exact value (includes/class-template-loader.php:819).

WCAG contrast guard (1.8.0)

Adopting an arbitrary theme brand colour and painting hard-coded white text on it fails WCAG AA on pale or muted brand colours (measured: 7 of 11 themes failed, Reign's lavender at 2.13:1). The fix cannot run in PHP - --jt-accent usually resolves to a theme token the browser only learns at runtime, so the server never sees the final colour. Instead the readable foreground is derived in CSS from whatever colour was adopted:

/* assets/css/jetonomy-tokens.css */
@supports (color: oklch(from red l c h)) {
    :root {
        --jt-accent-fg:       oklch(from var(--jt-accent)       clamp(0, (0.57 - l) * 1000, 1) 0 h);
        --jt-accent-hover-fg: oklch(from var(--jt-accent-hover) clamp(0, (0.57 - l) * 1000, 1) 0 h);
    }
}

It pulls the lightness (l) out of the accent, snaps it across the 0.57 threshold, and forces chroma 0 - so the text resolves to pure black on light accents and pure white on dark ones. The threshold is derived, not chosen: sweeping 0.40-0.80 against 11 real theme accents, 0.56-0.58 is the only band where all 11 clear AA. Hover gets its own derivation because --jt-accent-hover is the accent mixed 85% toward black and can flip the black/white choice. Buttons consume it - .jt-btn-fill { color: var(--jt-accent-fg); } in assets/css/jetonomy.css. The rule is feature-gated behind @supports; engines without relative-colour syntax fall back to the plain --jt-accent-fg: var(--jt-white) default declared in :root, so behaviour is never worse than before. The theme's brand colour itself is untouched; only the text on top of it adapts.

See the Theme Compatibility guide for the verified token map per theme, the compliance checklist, and guidance on themes that expose no recognizable brand token.


custom_css: freeform CSS from the admin

When the custom_css setting contains text, it is appended to the inline style block after the palette and inherit-colors rules. The value is passed through wp_strip_all_tags() before output, so HTML markup is stripped but valid CSS (including --jt-* token assignments) is preserved.

This is the right place for a site owner to make one-off tweaks without a child theme - for example:

/* In Jetonomy → Settings → Appearance → Custom CSS */
.jt-app {
    --jt-radius: 0;  /* square corners sitewide */
}
.jt-btn--primary {
    font-weight: 700;
}

The jetonomy_dynamic_css filter

Since 1.5.0.

The jetonomy_dynamic_css filter fires after the full dynamic CSS string is assembled - container width, palette overrides (palette_css()), layout-density rules, and the admin custom_css - and before it is attached to the page via wp_add_inline_style(). Note that the host-theme colour-adoption chain and the font tokens are not part of this string: they are declared statically in assets/css/jetonomy-tokens.css, not injected inline.

Use it from a plugin (companion plugin, add-on) to append token overrides or scoped rules without requiring a child theme.

Parameters

Parameter Type Description
$css string The assembled inline CSS string
$settings array The jetonomy_settings option array

Returns: string - the modified CSS string.

Source: includes/class-template-loader.php:264

add_filter( 'jetonomy_dynamic_css', function ( string $css, array $settings ): string {
    // Force a wider sidebar on a high-content site.
    $css .= '.jt-two-col { --jt-sidebar-width: 320px; }';
    return $css;
}, 10, 2 );

You can also branch on $settings to conditionally apply a rule:

add_filter( 'jetonomy_dynamic_css', function ( string $css, array $settings ): string {
    // Only tighten radius on spacious communities.
    if ( 'spacious' === ( $settings['layout_density'] ?? '' ) ) {
        $css .= ':root { --jt-radius: 4px; }';
    }
    return $css;
}, 10, 2 );

Child-theme overrides via :root

For theme-level token changes, override in your child theme's style.css (or an enqueued stylesheet) by targeting :root or .jt-app with a specificity that outranks the plugin's block:

/* your-child-theme/style.css */

/* Override the accent token for Jetonomy only. */
.jt-app {
    --jt-accent: #7c3aed;
    --jt-accent-hover: #6d28d9;
}

/* Or override globally (affects both Jetonomy and any component that reads the WP preset). */
:root {
    --wp--preset--color--primary: #7c3aed;
}

The .jt-app selector targets the Jetonomy container specifically without leaking into the rest of the page. The :root approach shares the change with any other plugin that reads the same WP preset token.

Both the static adoption chain and the admin accent override are declared on :root. A child-theme rule on .jt-app is more specific than :root, so it wins without !important; a child-theme rule on :root needs to load after the plugin's stylesheet (or carry higher specificity) to take precedence.


Dark-mode rule

Never write per-component dark selectors. Dark mode overrides belong only in the .jt-dark, [data-theme="dark"] block in assets/css/jetonomy-tokens.css, reassigning the --jt-* root tokens on the class the host theme puts on <body>. Individual components inherit dark mode automatically because they reference the tokens:

/* Correct */
.jt-card {
    background: var(--jt-bg);
    border: 1px solid var(--jt-border);
}

/* Wrong - breaks when a token value changes */
.jt-dark .jt-card {
    background: #1e1e1e;
}

What's next

Jetonomy's frontend runs on the WordPress Interactivity API (@wordpress/interactivity). The store is keyed 'jetonomy' and handles voting, navigation, bookmarks, moderation actions, and more. After every client-side page swap the store dispatches a jetonomy:navigated document event so companion scripts can re-initialize without duplicating nav logic. All REST calls go through a shared window.jetonomyRest.restFetch client.

This page explains how to extend the store, keep scripts alive across navigation, and call REST endpoints from your own JavaScript.

Source references:

  • Store definition: assets/js/view.js
  • jetonomy:navigated event: assets/js/view.js:776
  • REST client: assets/js/jetonomy-rest.js
  • Pagination hydration: assets/js/pagination-hydrator.min.js
  • Interactivity standard: docs/standards/frontend-interactivity.md

How the client-side router works

Every click inside #jetonomy-app is delegated to actions.navigate. The action decides whether the target URL is safe to swap client-side:

  • Most routes - swapped inside [data-wp-router-region="jetonomy/main"] without a full reload.
  • Rich-editor routes (/s/{slug}/t/{slug}/ single topic, /s/{slug}/new/ new post) - forced to full-page load because the composer and Prism.js bind on DOMContentLoaded and do not re-init on swap.

After each swap the navigate action dispatches jetonomy:navigated on document. Every companion script that targets region content must listen to this event; DOMContentLoaded alone is not reliable for region content.


Extending the Interactivity API store

Import store from @wordpress/interactivity inside a script module and pass the 'jetonomy' namespace to extend the existing store. New state, actions, and callbacks you add merge with the plugin's own entries; you do not replace them.

Register your module with wp_enqueue_script_module and declare jetonomy-view as a dependency so it loads after the core store.

// In your plugin's PHP boot file:
add_action( 'wp_enqueue_scripts', function () {
    wp_enqueue_script_module(
        'my-plugin-view',
        MY_PLUGIN_URL . 'assets/js/my-view.js',
        array( 'jetonomy-view' ),        // depends on the core store module
        MY_PLUGIN_VERSION
    );
} );
// assets/js/my-view.js  (ES module - loaded as a script module)
import { store, getContext } from '@wordpress/interactivity';

const { state, actions } = store( 'jetonomy', {
    state: {
        // Extend the shared state with your own reactive keys.
        myPluginPanelOpen: false,
    },

    actions: {
        toggleMyPanel() {
            state.myPluginPanelOpen = ! state.myPluginPanelOpen;
        },
    },
} );

Then bind your action declaratively in a template (or a template override):

<button
    data-wp-interactive="jetonomy"
    data-wp-on--click="actions.toggleMyPanel"
    data-wp-bind--aria-expanded="state.myPluginPanelOpen"
>
    Toggle
</button>
<div data-wp-bind--hidden="!state.myPluginPanelOpen">
    My plugin panel content
</div>

Declarative controls auto-hydrate after every client-side swap - no re-init code needed.


Re-initializing after jetonomy:navigated

Classic (non-module) scripts that need to wire up DOM nodes in the router region must listen to jetonomy:navigated alongside the initial load. This event fires immediately after each content swap.

The pattern is: guard your init function so it only wires freshly-swapped nodes (idempotent), and bind it to both startup and navigation.

// assets/js/my-classic-init.js
function initMyPlugin() {
    // Only target unwired nodes so re-running after navigation is safe.
    document.querySelectorAll( '.my-widget:not([data-my-wired])' ).forEach( function ( el ) {
        el.dataset.myWired = '1';
        el.addEventListener( 'click', function () {
            // handle click
        } );
    } );
}

// Startup - guard for cases where the DOM is already ready.
if ( document.readyState === 'loading' ) {
    document.addEventListener( 'DOMContentLoaded', initMyPlugin );
} else {
    initMyPlugin();
}

// Re-run after every client-side navigation.
document.addEventListener( 'jetonomy:navigated', initMyPlugin );

The jetonomy:navigated event is a CustomEvent with detail.href set to the navigated URL:

document.addEventListener( 'jetonomy:navigated', function ( event ) {
    console.log( 'navigated to', event.detail.href );
    initMyPlugin();
} );

Do NOT use DOMContentLoaded alone for content inside the router region. It fires once on the initial page load and never again after a client-side swap.


Complete worked example: "Quick-note" button that survives navigation

This example adds a per-post "Quick note" button to post cards (#03-template-overrides-jtnmy) or the jetonomy_post_card_after_badges hook) that opens a small text box and saves the note to the REST API.

1. Register the script module

add_action( 'wp_enqueue_scripts', function () {
    wp_enqueue_script_module(
        'my-quick-note',
        MY_PLUGIN_URL . 'assets/js/quick-note.js',
        array( 'jetonomy-view' ),
        MY_PLUGIN_VERSION
    );
} );

2. The script module (quick-note.js)

import { store } from '@wordpress/interactivity';

store( 'jetonomy', {
    state: {
        quickNoteOpen: {},    // keyed by post ID
        quickNoteText: {},
    },

    actions: {
        toggleQuickNote() {
            const postId = this.context.postId;
            const current = state.quickNoteOpen[ postId ] ?? false;
            state.quickNoteOpen[ postId ] = ! current;
        },

        async saveQuickNote() {
            const postId = this.context.postId;
            const text   = state.quickNoteText[ postId ] ?? '';
            if ( ! text.trim() ) return;

            const result = await window.jetonomyRest.restFetch(
                '/my-plugin/notes/' + postId,
                {
                    method: 'POST',
                    body:   { note: text },
                }
            );

            if ( result.ok ) {
                state.quickNoteOpen[ postId ] = false;
            }
        },
    },
} );

3. Markup added via hook

add_action( 'jetonomy_post_card_after_badges', function ( $post ) {
    printf(
        '<div data-wp-interactive="jetonomy" data-wp-context=\'{"postId": %d}\'>
            <button class="jt-btn jt-btn--ghost" data-wp-on--click="actions.toggleQuickNote">Note</button>
            <div data-wp-bind--hidden="!state.quickNoteOpen[context.postId]">
                <textarea data-wp-bind--value="state.quickNoteText[context.postId]"
                          data-wp-on--input="state.quickNoteText[context.postId] = event.target.value">
                </textarea>
                <button class="jt-btn jt-btn--primary" data-wp-on--click="actions.saveQuickNote">Save</button>
            </div>
        </div>',
        (int) $post->id
    );
}, 10, 1 );

Because both the state and the actions are declared in the 'jetonomy' store, the Interactivity API automatically re-hydrates the directives after every client-side navigation - no jetonomy:navigated listener is needed here.


Using window.jetonomyRest.restFetch

window.jetonomyRest.restFetch is a shared REST client available on every Jetonomy community page. It:

  • Resolves the REST base URL and nonce from window.jetonomyData.
  • Sends credentials: 'same-origin' and the X-WP-Nonce header on every request.
  • JSON-encodes plain-object bodies and sets Content-Type: application/json.
  • On 403 rest_cookie_invalid_nonce responses, fetches /jetonomy/v1/auth/nonce to refresh the nonce and retries the original request automatically.
  • Never throws - always resolves to { ok: boolean, status: number, data: any }.

Signature:

const result = await window.jetonomyRest.restFetch( path, options );
// path - string, e.g. '/posts/42/vote' or 'posts/42' (leading slash optional)
// options - fetch-compatible object: method, body, headers, etc.
// result - { ok, status, data }

Reading example:

const result = await window.jetonomyRest.restFetch( '/spaces/12/posts?per_page=5' );
if ( result.ok ) {
    const posts = result.data.items;
}

Mutation example:

const result = await window.jetonomyRest.restFetch( '/posts/42/vote', {
    method: 'POST',
    body:   { direction: 'up' },
} );
if ( ! result.ok ) {
    console.error( 'Vote failed', result.status, result.data );
}

window.jetonomyHydrateInteractive

When you append new DOM nodes that contain data-wp-on--click directives (for example, via a "Load More" fetch), the Interactivity API does not automatically re-wire those nodes. Call window.jetonomyHydrateInteractive( nodes ) with an array of the newly appended elements and the pagination hydrator will make their click handlers work.

fetch( nextPageUrl )
    .then( r => r.text() )
    .then( html => {
        const doc      = new DOMParser().parseFromString( html, 'text/html' );
        const newItems = Array.from( doc.querySelectorAll( '.jt-row' ) );
        const list     = document.querySelector( '.jt-topics' );

        newItems.forEach( item => list.appendChild( item ) );

        if ( typeof window.jetonomyHydrateInteractive === 'function' ) {
            window.jetonomyHydrateInteractive( newItems );
        }
    } );

This is used internally by the Load More pagination handler (assets/js/pagination-frontend.min.js).


Compliance checklist

Before shipping any frontend feature, verify against the Frontend Interactivity Standard:

  • No DOMContentLoaded-only handler targeting region content without a jetonomy:navigated pair.
  • No wp_add_inline_script / inline <script> driving region behavior.
  • No raw fetch() calls - use window.jetonomyRest.restFetch instead.
  • Interactive controls use data-wp-on--* store actions wherever possible.
  • Verify the feature after a client-side navigation, not only after a full page load.

What's next

Jetonomy's REST API lives at jetonomy/v1. This page covers three extension points: adding fields to an existing response via the jetonomy_rest_prepare_* filters, registering a new endpoint in the same namespace, and hooking into the content moderation intercept jetonomy_check_content.

Source references:

  • Response filters: includes/api/class-posts-controller.php:1140, class-replies-controller.php:782, class-spaces-controller.php:1135, class-users-controller.php:250, class-notifications-controller.php:323
  • Auth helper: includes/api/class-rest-auth.php
  • Moderation intercept: includes/api/class-posts-controller.php:510, class-replies-controller.php:292
  • Base controller: includes/api/class-base-controller.php

Adding a field to an existing response

Each core object type exposes a filter on the prepared response array immediately before it is returned to the client. Hook in, add your key, return the array.

Available prepare filters

Filter Object type Source
jetonomy_rest_prepare_post Post row class-posts-controller.php:1140
jetonomy_rest_prepare_reply Reply row class-replies-controller.php:782
jetonomy_rest_prepare_space Space row class-spaces-controller.php:1135
jetonomy_rest_prepare_user WP_User class-users-controller.php:250
jetonomy_rest_prepare_notification Notification row class-notifications-controller.php:323

Parameters (all five filters share this shape):

Parameter Type Description
$data array The prepared response data array - modify and return this
$object object The raw model row (or WP_User for the user filter)
$request WP_REST_Request|null The originating request; null in non-request contexts

Returns: array - the modified data array.

Example: append a gamification score to post responses

add_filter( 'jetonomy_rest_prepare_post', function ( array $data, object $post, $request ): array {
    // Add a field from an external gamification plugin.
    $data['wb_gam_score'] = (int) WB_Gam\Scores::for_post( (int) $post->id );
    return $data;
}, 10, 3 );

Example: append custom field values to replies

add_filter( 'jetonomy_rest_prepare_reply', function ( array $data, object $reply, $request ): array {
    // Attach a "sentiment" label computed by a text-analysis service.
    $data['sentiment'] = get_post_meta( (int) $reply->id, 'my_sentiment', true ) ?: 'neutral';
    return $data;
}, 10, 3 );

Example: add a badge count to user responses

add_filter( 'jetonomy_rest_prepare_user', function ( array $data, \WP_User $user, $request ): array {
    $data['badge_count'] = (int) My_Badges::count_for_user( $user->ID );
    return $data;
}, 10, 3 );

The frontend JavaScript reads response data via window.jetonomyRest.restFetch. Fields you add here are available in result.data without any further wiring.


Registering a new jetonomy/v1 route

Companion plugins can register additional endpoints inside the jetonomy/v1 namespace on rest_api_init. This keeps all community API calls under one namespace and nonce context.

Permission callbacks

Every mutation route (POST, PUT, PATCH, DELETE) must use \Jetonomy\API\REST_Auth::auth_mutation() as its permission_callback. Do not use raw closures, is_user_logged_in(), or current_user_can() - the audit script enforces this rule.

For read-only routes, use \Jetonomy\Visibility::rest_check (respects the Private Community setting) or '__return_true' for fully public data.

Since 1.6.0, REST_Auth::auth_mutation() (and the Pro wrapper rest_auth_mutation()) also hard-rejects banned users (jetonomy_user_banned) and users still pending email verification (jetonomy_pending_verification) with a 403, before the capability check runs. This holds even for Application-Password sessions. Extension authors inherit this account-status protection automatically by using the standard callback - there is nothing extra to wire.

Important for companion plugins: REST_Auth is defined in the free plugin. If your plugin may be active before Jetonomy, resolve the class lazily inside the callback rather than in the registration call. Eager static calls at route-registration time can fatal the REST API if the free plugin loads later.

Basic read endpoint

add_action( 'rest_api_init', function () {
    register_rest_route(
        'jetonomy/v1',
        '/my-plugin/stats',
        array(
            'methods'             => \WP_REST_Server::READABLE,
            'callback'            => 'my_plugin_stats_handler',
            'permission_callback' => array( \Jetonomy\Visibility::class, 'rest_check' ),
        )
    );
} );

function my_plugin_stats_handler( \WP_REST_Request $request ): \WP_REST_Response {
    return rest_ensure_response( array(
        'total_posts' => My_Plugin::count_posts(),
    ) );
}

Mutation endpoint with auth

add_action( 'rest_api_init', function () {
    register_rest_route(
        'jetonomy/v1',
        '/my-plugin/notes/(?P<post_id>\d+)',
        array(
            array(
                'methods'             => \WP_REST_Server::CREATABLE,
                'callback'            => 'my_plugin_save_note',
                // Lazy resolution - safe if Jetonomy loads after this plugin.
                'permission_callback' => function ( \WP_REST_Request $r ) {
                    if ( ! class_exists( \Jetonomy\API\REST_Auth::class ) ) {
                        return new \WP_Error( 'jetonomy_not_loaded', 'Jetonomy is required.', array( 'status' => 503 ) );
                    }
                    return call_user_func( \Jetonomy\API\REST_Auth::auth_mutation( 'read' ) );
                },
                'args'                => array(
                    'post_id' => array( 'type' => 'integer', 'required' => true, 'minimum' => 1 ),
                    'note'    => array( 'type' => 'string',  'required' => true, 'sanitize_callback' => 'sanitize_textarea_field' ),
                ),
            ),
        )
    );
} );

function my_plugin_save_note( \WP_REST_Request $request ): \WP_REST_Response {
    $post_id = (int) $request->get_param( 'post_id' );
    $note    = (string) $request->get_param( 'note' );
    $user_id = get_current_user_id();

    My_Plugin::save_note( $post_id, $user_id, $note );

    return rest_ensure_response( array( 'saved' => true ) );
}

The endpoint is then callable from window.jetonomyRest.restFetch:

const result = await window.jetonomyRest.restFetch(
    '/my-plugin/notes/' + postId,
    { method: 'POST', body: { note: 'Great post' } }
);

Content moderation intercept: jetonomy_check_content

The jetonomy_check_content filter fires inside the post and reply controllers immediately before a new item is saved, giving you a chance to route it to a moderation state. It is the correct place to hook an AI spam detector, a profanity filter, or a word-list checker.

Parameters

Parameter Type Description
$action string|null The current action; null on entry (pass-through). Return a value to override.
$data array The content data. For posts: ['title' => string, 'content' => string, ...]. For replies: ['content' => string, ...].
$space_id int The space the content is being posted to
$user_id int The author's WP user ID

Returns: one of:

Return value Effect
null No action - content saves normally
'hold' Status set to pending; moves to the moderation queue
'spam' Status set to spam
'flag' Content publishes but a Flag record is created; it surfaces in the moderation queue for review
'block' Request returns a 400 error to the author; content is not saved

Source: includes/api/class-posts-controller.php:510, class-replies-controller.php:292

Example: hold posts from new users

add_filter( 'jetonomy_check_content', function ( $action, array $data, int $space_id, int $user_id ) {
    if ( ! is_null( $action ) ) {
        return $action; // Respect a decision already made (e.g. by a higher-priority listener).
    }

    // Hold any post from a user registered less than 24 hours ago.
    $user = get_userdata( $user_id );
    if ( $user && ( time() - strtotime( $user->user_registered ) ) < DAY_IN_SECONDS ) {
        return 'hold';
    }

    return null;
}, 10, 4 );

Example: spam-detect via an external API

add_filter( 'jetonomy_check_content', function ( $action, array $data, int $space_id, int $user_id ) {
    if ( ! is_null( $action ) ) {
        return $action;
    }

    $content = ( $data['title'] ?? '' ) . ' ' . ( $data['content'] ?? '' );
    $verdict = My_Spam_Service::check( $content, $user_id );

    if ( 'spam' === $verdict ) {
        return 'spam';
    }
    if ( 'suspicious' === $verdict ) {
        return 'hold';
    }

    return null;
}, 15, 4 ); // Priority 15 so it runs after trust-level checks (priority 10).

Example: hard-block prohibited phrases

add_filter( 'jetonomy_check_content', function ( $action, array $data, int $space_id, int $user_id ) {
    if ( 'block' === $action ) {
        return $action; // Already blocked by another listener.
    }

    $text       = strtolower( ( $data['title'] ?? '' ) . ' ' . ( $data['content'] ?? '' ) );
    $prohibited = array( 'buy cheap', 'click here to win', 'guaranteed income' );

    foreach ( $prohibited as $phrase ) {
        if ( str_contains( $text, $phrase ) ) {
            return 'block';
        }
    }

    return $action;
}, 10, 4 );

Note: the jetonomy_check_content filter described here differs from the jetonomy_check_content filter in Hooks Reference §Filter Hooks which returns true|WP_Error. That filter is the older surface; this filter returns a string action and is the one used by the controllers for moderation routing.


What's next

  • Hooks Reference - action and filter hooks fired around every lifecycle event
  • Extend the Frontend - call your new endpoint from JavaScript
  • Adapters - replace the email, search, or real-time adapter with a custom implementation

Jetonomy's notification emails flow through a single pipeline: event → Notifier → subject/body resolution → filters → HTML render → email adapter. Every stage exposes a filter so you can adjust any aspect - subject line, body text, logo, accent color, headers, or the full rendered HTML - without replacing core template files.

Source references:

  • Notifier::send_email_notification(): includes/notifications/class-notifier.php:926
  • Notifier::render_email_template(): includes/notifications/class-notifier.php:1093
  • Notifier::locate_email_template(): includes/notifications/class-notifier.php:1040
  • Architecture overview: docs/developer/EMAILS.md

Notification types

Each type has its own subject + body template and its own web/email on/off toggle in user preferences.

Type slug When it fires
reply_to_post Someone replies to a post you authored
reply_to_reply Someone replies to a reply you authored
mention @you appears in a post or reply
accepted_answer Your reply was accepted as the answer on a Q&A post
new_post_in_sub A new post appears in a space you are subscribed to
vote_on_post Someone votes on your post
moderation A moderator takes an action on your content
join_request Someone asks to join a space you administer
badge_earned You earn a badge (Pro)

Placeholder tokens (admin templates)

Under Jetonomy → Settings → Email → Email Templates, admins can override the subject and body for each type. The following tokens are substituted before the filters fire:

Token Expands to
{site} Site name from get_bloginfo('name')
{user} Recipient's display name
{message} The system-computed event message
{type} The notification type slug
{url} The deep-link URL for the event
{post_title} Title of the related post (when available)
{actor_display_name} Display name of the person who triggered the event
{reply_excerpt} Excerpt from the related reply (when available)
{space_title} Title of the related space (when available)

Enriched tokens ({post_title}, {actor_display_name}, {reply_excerpt}, {space_title}) fall back to an empty string when the event has no related content, so existing templates never render a literal {token} string.


Developer filters

All filters live on Notifier::send_email_notification() or Notifier::render_email_template(). They run after admin template overrides and placeholder substitution.

jetonomy_email_subject

Filter the email subject line before sending.

Parameters

Parameter Type Description
$subject string Computed subject after placeholder substitution
$type string Notification type slug
$user WP_User Notification recipient

Returns: string

Source: includes/notifications/class-notifier.php:988

add_filter( 'jetonomy_email_subject', function ( string $subject, string $type, \WP_User $user ): string {
    if ( 'mention' === $type ) {
        return '[Mention] ' . $user->display_name . ' - you were tagged in the community';
    }
    return $subject;
}, 10, 3 );

jetonomy_email_body

Filter the plain-text email body (the intro sentence shown above the CTA button). Placeholder substitution has already run.

Parameters

Parameter Type Description
$body string Rendered body text
$type string Notification type slug
$user WP_User Notification recipient

Returns: string

Source: includes/notifications/class-notifier.php:998

add_filter( 'jetonomy_email_body', function ( string $body, string $type, \WP_User $user ): string {
    // Append a reply-by-email instruction for reply notifications.
    if ( in_array( $type, array( 'reply_to_post', 'reply_to_reply' ), true ) ) {
        $body .= "\n\nTo reply, visit the community - reply-by-email is not supported.";
    }
    return $body;
}, 10, 3 );

jetonomy_email_headers

Filter the headers array passed to the email adapter for all notification emails.

Parameters

Parameter Type Description
$headers string[] Array of mail header strings (RFC 2822 format)
$type string Notification type slug
$user WP_User Notification recipient

Returns: array

Source: includes/notifications/class-notifier.php:1017

The default headers already include List-Unsubscribe and List-Unsubscribe-Post (RFC 8058). Use this filter to append additional headers:

add_filter( 'jetonomy_email_headers', function ( array $headers, string $type, \WP_User $user ): array {
    $headers[] = 'X-Community-Type: ' . $type;
    $headers[] = 'X-Community-User: ' . $user->ID;
    return $headers;
}, 10, 3 );

jetonomy_email_html

Filter the fully rendered HTML of the notification email immediately before it is passed to the email adapter.

Parameters

Parameter Type Description
$html string Full rendered HTML string
$type string Notification type slug
$user WP_User Notification recipient

Returns: string

Source: includes/notifications/class-notifier.php:1208

add_filter( 'jetonomy_email_html', function ( string $html, string $type, \WP_User $user ): string {
    // Inject a 1×1 tracking pixel before </body>.
    $pixel_url = esc_url( add_query_arg( array(
        'type' => $type,
        'uid'  => $user->ID,
    ), 'https://analytics.example.com/pixel.gif' ) );
    $pixel = '<img src="' . $pixel_url . '" width="1" height="1" alt="" style="display:none">';
    return str_replace( '</body>', $pixel . '</body>', $html );
}, 10, 3 );

jetonomy_email_accent_color

Filter the hex accent color used in notification email templates for the header bar and CTA button. Defaults to the accent_color setting, falling back to #3B82F6.

Parameters

Parameter Type Description
$color string Hex color string (e.g. '#3B82F6')
$type string Notification type slug

Returns: string

Source: includes/notifications/class-notifier.php:1109

// Use a red accent for moderation emails to signal urgency.
add_filter( 'jetonomy_email_accent_color', function ( string $color, string $type ): string {
    return 'moderation' === $type ? '#DC2626' : $color;
}, 10, 2 );

jetonomy_email_logo_url

Filter the logo URL rendered in the email header. Return an empty string to fall back to the site name in plain text.

Parameters

Parameter Type Description
$url string Logo URL (empty string if none configured)
$type string Notification type slug

Returns: string

Source: includes/notifications/class-notifier.php:1119

add_filter( 'jetonomy_email_logo_url', function ( string $url, string $type ): string {
    // Serve a CDN-hosted 2x logo regardless of the admin setting.
    return 'https://cdn.example.com/email-logo@2x.png';
}, 10, 2 );

jetonomy_email_template_context

Filter the full context array passed into the email template renderer before the template file is included. Use this to inject additional variables that your custom template files (or a per-type template override) can read.

Parameters

Parameter Type Description
$ctx array Template variables array (see keys below)
$type string Notification type slug
$user WP_User Notification recipient

Returns: array

Source: includes/notifications/class-notifier.php:1191

Standard keys in $ctx:

Key Type Description
type string Notification type slug
type_label string Human-readable label (e.g. 'New Reply')
site_name string HTML-escaped site name
home_url string Escaped home URL
community_url string URL for the event's deep link
notif_url string URL to the notifications page
unsub_url string Signed unsubscribe URL
accent string Raw hex accent color
logo_url string Escaped logo URL (empty string if none)
cta_text string CTA button label
cta_url string CTA button URL
message string Rendered event message (raw; template escapes it)
footer_text string Footer line from settings
post_title string Related post title (empty if not applicable)
actor_display_name string Name of the person who triggered the event
reply_excerpt string Excerpt from the triggering reply
space_title string Title of the related space
user WP_User Recipient user object
add_filter( 'jetonomy_email_template_context', function ( array $ctx, string $type, \WP_User $user ): array {
    // Add the user's membership tier so the template can render a personalized footer.
    $ctx['membership_tier'] = My_Membership::get_tier_label( $user->ID );
    return $ctx;
}, 10, 3 );

jetonomy_email_template_path

Filter the resolved template file path before it is included. The lookup order before this filter is: child-theme override (yourtheme/jetonomy/emails/{type}.php) → plugin per-type (templates/emails/{type}.php) → child-theme base → plugin base (templates/emails/base.php).

Parameters

Parameter Type Description
$path string Absolute filesystem path to the template file
$type string Notification type slug

Returns: string

Source: includes/notifications/class-notifier.php:1076

// Point a specific type to a completely custom template.
add_filter( 'jetonomy_email_template_path', function ( string $path, string $type ): string {
    if ( 'mention' === $type ) {
        return MY_PLUGIN_DIR . 'templates/email-mention.php';
    }
    return $path;
}, 10, 2 );

Overriding a template file in a child theme

Without any PHP code, create a file at:

your-theme/jetonomy/emails/{type}.php

where {type} is the notification type slug with underscores replaced by hyphens - for example reply-to-post.php for the reply_to_post type. If no per-type file exists, base.php is used.

Inside the file you have access to all variables from the $ctx array above (extracted into local scope). Follow the structure of wp-content/plugins/jetonomy/templates/emails/base.php as your starting point.


Replacing the email adapter

To send Jetonomy emails through SMTP, SendGrid, Postmark, or any other provider, register a custom adapter. The adapter must implement \Jetonomy\Adapters\Email_Adapter (see includes/adapters/interface-email-adapter.php):

add_action( 'init', function () {
    \Jetonomy\Adapters\Adapter_Registry::register_email(
        'my-smtp',
        new My_Plugin\SMTP_Adapter()
    );
} );
// My_Plugin\SMTP_Adapter:
class SMTP_Adapter implements \Jetonomy\Adapters\Email_Adapter {
    public function send(
        string $to,
        string $subject,
        string $html,
        string $plain,
        array  $headers
    ): bool {
        return My_SMTP_Service::send( $to, $subject, $html, $headers );
    }
}

See Adapters for the full adapter pattern documentation.


What's next

Jetonomy registers its admin UI under a single top-level menu (Jetonomy → Dashboard / Spaces / Categories / Users / Moderation / Settings / Import). Every admin page exposes action and filter hooks so companion plugins and Pro extensions can inject tabs, widgets, and settings panels without patching core files.

Admin Pages at a Glance

Page WP menu slug Extension hooks
Dashboard jetonomy-dashboard jetonomy_admin_dashboard_widgets, jetonomy_admin_dashboard_after_stats
Spaces list jetonomy-spaces (no list-table column hooks - see Known Gap)
Space edit jetonomy-spaces&action=edit&id=N jetonomy_admin_space_edit_tabs, jetonomy_admin_space_edit_tab_content
Settings jetonomy-settings jetonomy_admin_settings_tabs, jetonomy_admin_settings_tab_content
Moderation jetonomy-moderation jetonomy_admin_moderation_tabs, jetonomy_admin_moderation_tab_content
All pages n/a jetonomy_admin_menu_label, jetonomy_admin_menu_icon, jetonomy_admin_footer_text

All hook signatures on this page are verified from source. Where the summary table in 02-hooks-reference.md differs (it lists some parameters as "none"), the source file is the authoritative reference.


Settings Page Tabs

The Settings page (Jetonomy → Settings) has eight built-in primary tabs: general, permissions, email, appearance, seo, antispam, free-vs-pro, and license. Two hooks let you add more.

jetonomy_admin_settings_tabs

Fires inside the Settings page tab bar. Output a <a class="nav-tab"> element to add a new entry.

Source: includes/admin/views/settings.php

Parameter Type Description
$active_tab string The currently active tab slug (from $_GET['tab'], defaulting to 'general')
add_action( 'jetonomy_admin_settings_tabs', function( string $active_tab ) {
    $class = 'acme-settings' === $active_tab ? 'nav-tab-active' : '';
    printf(
        '<a href="%s" class="nav-tab %s">%s</a>',
        esc_url( admin_url( 'admin.php?page=jetonomy-settings&tab=acme-settings' ) ),
        esc_attr( $class ),
        esc_html__( 'Acme', 'acme' )
    );
} );

jetonomy_admin_settings_tab_content

Fires inside the Settings page content area. Guard your output with a slug check; other tab content hooks will also receive the callback.

The Settings view output-buffers this hook so any .notice divs you emit are automatically hoisted above the page layout. You do not need to handle hoisting yourself.

Source: includes/admin/views/settings.php

Parameter Type Description
$active_tab string The currently active tab slug
add_action( 'jetonomy_admin_settings_tab_content', function( string $active_tab ) {
    if ( 'acme-settings' !== $active_tab ) {
        return;
    }

    $limit = (int) get_option( 'acme_widget_limit', 5 );
    ?>
    <div class="wrap jetonomy-settings-wrap">
        <form method="post">
            <?php wp_nonce_field( 'acme_settings_save', 'acme_nonce' ); ?>
            <table class="form-table">
                <tr>
                    <th scope="row">
                        <label for="acme_widget_limit">
                            <?php esc_html_e( 'Widget limit', 'acme' ); ?>
                        </label>
                    </th>
                    <td>
                        <input
                            type="number" min="1" max="50"
                            id="acme_widget_limit"
                            name="acme_widget_limit"
                            value="<?php echo esc_attr( $limit ); ?>"
                            class="small-text"
                        >
                    </td>
                </tr>
            </table>
            <?php submit_button(); ?>
        </form>
    </div>
    <?php
} );

// Save handler - runs before the view (admin_init fires earlier than the page render).
add_action( 'admin_init', function() {
    if (
        isset( $_POST['acme_nonce'] ) &&
        wp_verify_nonce( sanitize_key( $_POST['acme_nonce'] ), 'acme_settings_save' ) &&
        current_user_can( 'manage_options' )
    ) {
        update_option( 'acme_widget_limit', absint( $_POST['acme_widget_limit'] ?? 5 ) );
        add_settings_error( 'acme', 'acme_saved', __( 'Settings saved.', 'acme' ), 'updated' );
        set_transient( 'settings_errors', get_settings_errors(), 30 );
    }
} );

Space Edit Page Tabs

The space edit page (Spaces → Edit) shows built-in tabs for General, SEO, Members, Access Rules, and Moderation. Jetonomy Pro injects its Custom Fields, Reactions, and AI tabs through these same hooks.

jetonomy_admin_space_edit_tabs

Fires inside the space edit tab bar. Output a <a class="nav-tab"> element.

Source: includes/admin/views/space-edit.php

Parameter Type Description
$space object The space row object being edited. Properties include id, name, slug, description, status, type, settings
add_action( 'jetonomy_admin_space_edit_tabs', function( object $space ) {
    $active_tab = sanitize_key( $_GET['tab'] ?? 'general' );
    $class      = 'acme-space-tab' === $active_tab ? 'nav-tab-active' : '';
    printf(
        '<a href="%s" class="nav-tab %s">%s</a>',
        esc_url( add_query_arg( 'tab', 'acme-space-tab' ) ),
        esc_attr( $class ),
        esc_html__( 'Acme', 'acme' )
    );
} );

jetonomy_admin_space_edit_tab_content

Fires inside the space edit content area. Receives both the active tab slug and the full space object.

Source: includes/admin/views/space-edit.php

Parameter Type Description
$active_tab string The currently active tab slug
$space object The space row object
add_action( 'jetonomy_admin_space_edit_tab_content', function( string $active_tab, object $space ) {
    if ( 'acme-space-tab' !== $active_tab ) {
        return;
    }

    echo '<div class="jetonomy-tab-content">';
    printf(
        '<h3>%s %s</h3>',
        esc_html__( 'Acme settings for:', 'acme' ),
        esc_html( $space->name )
    );
    // Your per-space form here. Read/write space settings via the REST API
    // or store your own data in wp_options keyed by $space->id.
    echo '</div>';
}, 10, 2 );

Moderation Page Tabs

The Moderation page shows Posts, Replies, and Flags tabs. Jetonomy Pro hooks its Auto-Rules tab here.

jetonomy_admin_moderation_tabs

Fires inside the Moderation page tab bar.

Source: includes/admin/views/moderation.php

Parameter Type Description
$active_tab string The currently active tab slug (e.g. 'posts', 'replies', 'flags')

jetonomy_admin_moderation_tab_content

Fires inside the Moderation page content area.

Source: includes/admin/views/moderation.php

Parameter Type Description
$active_tab string The currently active tab slug
// Register the moderation tab nav item.
add_action( 'jetonomy_admin_moderation_tabs', function( string $active_tab ) {
    $class = 'acme-mod-panel' === $active_tab ? 'nav-tab-active' : '';
    printf(
        '<a href="%s" class="nav-tab %s">%s</a>',
        esc_url( add_query_arg( 'tab', 'acme-mod-panel' ) ),
        esc_attr( $class ),
        esc_html__( 'Acme Rules', 'acme' )
    );
} );

// Render the moderation tab content.
add_action( 'jetonomy_admin_moderation_tab_content', function( string $active_tab ) {
    if ( 'acme-mod-panel' !== $active_tab ) {
        return;
    }

    echo '<div class="wrap">';
    echo '<h2>' . esc_html__( 'Acme Moderation Rules', 'acme' ) . '</h2>';
    // Your moderation panel here.
    echo '</div>';
} );

Dashboard Widgets

jetonomy_admin_dashboard_widgets

Fires inside the dashboard grid column, after the built-in Recent Activity and Trending cards. Jetonomy Pro hooks its analytics panel here. No parameters.

Source: includes/admin/views/dashboard.php

add_action( 'jetonomy_admin_dashboard_widgets', function() {
    echo '<div class="jetonomy-dashboard-card">';
    echo '<h2>' . esc_html__( 'Acme Stats', 'acme' ) . '</h2>';
    echo '<p>' . esc_html( acme_get_stat_summary() ) . '</p>';
    echo '</div>';
} );

jetonomy_admin_dashboard_after_stats

Fires immediately after the top-row stat cards and before the dashboard grid. Use this for a wide banner, a secondary summary row, or a notice that should span the full column width.

Source: includes/admin/views/dashboard.php

Parameter Type Description
$stats array Site-wide totals. Keys: total_posts (int), total_replies (int), active_spaces (int), users (int), pending_flags (int), posts_today (int)
add_action( 'jetonomy_admin_dashboard_after_stats', function( array $stats ) {
    if ( $stats['pending_flags'] > 10 ) {
        printf(
            '<div class="notice notice-warning inline"><p>%s</p></div>',
            esc_html( sprintf(
                /* translators: %d: number of pending flags */
                __( '%d flags are waiting for review.', 'acme' ),
                $stats['pending_flags']
            ) )
        );
    }
} );

Three filters let you rebrand how Jetonomy appears in the wp-admin sidebar. All three are filters, not actions.

jetonomy_admin_menu_label

Filters the top-level admin menu label. Default: 'Jetonomy'.

Source: includes/admin/class-admin.php

add_filter( 'jetonomy_admin_menu_label', fn() => 'Community' );

jetonomy_admin_menu_icon

Filters the admin menu icon. Default is the Jetonomy SVG glyph encoded as a data:image/svg+xml;base64,… URI. Return any Dashicons class string or your own data: URI.

Source: includes/admin/class-admin.php

// Use a Dashicons icon.
add_filter( 'jetonomy_admin_menu_icon', fn() => 'dashicons-groups' );

// Or supply a custom SVG.
add_filter( 'jetonomy_admin_menu_icon', function(): string {
    $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><!-- ... --></svg>';
    return 'data:image/svg+xml;base64,' . base64_encode( $svg );
} );

jetonomy_admin_footer_text

Filters the footer text shown at the bottom of every Jetonomy admin page, replacing the default WordPress "Thank you for creating with WordPress" line on Jetonomy pages only.

Source: includes/admin/class-admin.php

Parameter Type Description
$text string Default footer text

Return: string

add_filter( 'jetonomy_admin_footer_text', function( string $text ): string {
    return sprintf(
        /* translators: %1$s: brand name, %2$s: support link */
        __( 'Powered by %1$s &mdash; %2$s', 'acme' ),
        '<strong>' . esc_html__( 'Acme Community Suite', 'acme' ) . '</strong>',
        '<a href="https://example.com/support/" target="_blank">' . esc_html__( 'Get support', 'acme' ) . '</a>'
    );
} );

Known Gap: Admin List-Table Columns

There is no filter to add a column to any Jetonomy admin list table (Spaces, Posts, Replies, Users, Activity Log). The column definitions in each list table are hard-coded. If you need to surface per-row data:

  • Inject a notice or summary panel via jetonomy_admin_dashboard_after_stats (dashboard) or a custom admin page registered under the Jetonomy menu with add_submenu_page.
  • Add a space-level panel via jetonomy_admin_space_edit_tab_content on a new tab.
  • Store per-post or per-space data and expose it through the REST API; the admin list tables do not yet expose column hooks.

Column hooks are planned but not yet landed.


What's Next?

A reference for developers who know BuddyPress or BuddyBoss and want to understand how Jetonomy maps to those concepts. This page covers concept-level differences, API equivalents for common tasks, and an honest account of where no equivalent exists.

If you want to run Jetonomy alongside an active BuddyPress install, start with the BuddyPress Integration developer reference, which covers the coexistence layer (group-to-space pairing, member sync, activity broadcast, and the comment-to-reply bridge).


Concept Map

BuddyPress / BuddyBoss Jetonomy Notes
Group Space The primary container for discussion. BP groups are general-purpose social containers; Jetonomy spaces are forum/Q&A/ideas/feed containers with a configurable type.
Group topic / reply (bbPress) Post / Reply Jetonomy stores posts and replies in custom tables, not as WordPress CPTs.
Activity stream (no equivalent) Jetonomy has no site-wide activity stream. When BuddyPress is active, new posts broadcast to the paired group's activity stream via the integration layer. See BuddyPress Integration.
Extended profile field (xprofile) Pro Custom Fields Custom field definitions per object type (post, user, space) via the Jetonomy Pro custom-fields extension.
Member type (bp_register_member_type) Trust Levels + WP roles Jetonomy uses a 0-5 trust ladder (auto-evaluated from activity) plus standard WP roles. There is no named member-type registry.
Group forum tab (BP_Group_Extension) Space tab (jetonomy_space_tabs filter) See Add a Space Tab.
Profile tab / nav item (bp_core_new_nav_item) Profile tab (jetonomy_profile_tabs filter) See Add a Profile Tab.
Community nav link jetonomy_header_nav_items action See Add a Nav Item.
Group visibility (public / private / hidden) Space visibility (public / private) Jetonomy does not have a "hidden" equivalent. Private spaces are invisible to non-members; there is no invitation-only discovery mode.
bp_user_can / bp_current_user_can Jetonomy\Permissions\Permission_Engine::can() Per-action, per-space capability check. See Visibility and Access Matrix.
Community public/private toggle Jetonomy\Visibility::can_view_community() Community-level read gate (public = anyone can read, private = logged-in only). Separate from per-space access rules.
Member directory + facet filters (no equivalent) Jetonomy has jetonomy_search_query_args for content search but no member directory or faceted member browser.
Cover images (no equivalent) Jetonomy user profiles and spaces do not have cover images.
Notification preference hooks (no equivalent) Jetonomy's notification preference UI has no injection hook. The email adapter is swappable (#05-adapters-jtnmy)) but there is no per-type preference UI hook.

How Do I Do X?

Profile and navigation

BuddyPress / BuddyBoss Jetonomy equivalent Reference
bp_core_new_nav_item() - add a profile tab jetonomy_profile_tabs filter 12-add-a-profile-tab.md
BP_Group_Extension - add a space/group tab jetonomy_space_tabs filter 13-add-a-space-tab.md
bp_core_add_nav_item() - add a community nav link jetonomy_header_nav_items action 14-add-a-nav-item.md
bp_core_get_user_domain( $user_id ) \Jetonomy\get_profile_url( $user_id ) Filterable via jetonomy_profile_url

Membership and access

BuddyPress / BuddyBoss Jetonomy equivalent Notes
groups_is_user_member( $user_id, $group_id ) \Jetonomy\Models\SpaceMember::is_member( $space_id, $user_id ) Returns bool
bp_user_can( $user_id, 'publish_posts', 'groups' ) \Jetonomy\Permissions\Permission_Engine::can( $user_id, 'create_posts', $space_id ) Action strings: create_posts, vote, flag, moderate, manage_space, etc.
groups_is_user_admin( $user_id, $group_id ) \Jetonomy\Models\SpaceMember::get_role( $space_id, $user_id ) === 'admin' Roles: 'member', 'moderator', 'admin'
bp_is_user_in_group() (template tag) \Jetonomy\Models\SpaceMember::is_member( $space_id, get_current_user_id() ) n/a
bp_register_member_type( 'premium' ) WP add_role() / add_cap() + Jetonomy trust level evaluation No named member-type registry; use WP roles or trust levels

Content and spaces

BuddyPress / BuddyBoss Jetonomy equivalent Reference
groups_create_group( $args ) REST POST /jetonomy/v1/spaces or \Jetonomy\Models\Space::create( $data ) 01-rest-api.md
groups_get_groups( $args ) REST GET /jetonomy/v1/spaces or \Jetonomy\Models\Space::list( $args ) 01-rest-api.md
groups_get_group( $group_id ) \Jetonomy\Models\Space::find( $space_id ) n/a
bp_activity_add( $args ) REST POST /jetonomy/v1/spaces/{id}/posts or \Jetonomy\Models\Post::create( $data ) 01-rest-api.md
bp_activity_delete( $args ) REST DELETE /jetonomy/v1/posts/{id} 01-rest-api.md
xprofile_set_field_data( $field, $user_id, $value ) Pro Custom Fields extension - REST PUT /jetonomy/v1/users/{id}/custom-fields Pro only

Hooks

BuddyPress / BuddyBoss hook Jetonomy equivalent Reference
groups_join_group jetonomy_user_joined_space 02-hooks-reference.md
groups_leave_group / groups_remove_member jetonomy_user_left_space 02-hooks-reference.md
groups_promote_member (no direct equivalent) Listen to jetonomy_user_joined_space with $role = 'admin' or 'moderator'
bp_activity_comment_posted jetonomy_after_create_reply 02-hooks-reference.md
bp_activity_action (activity posted) jetonomy_after_create_post 02-hooks-reference.md
bp_get_user_domain jetonomy_profile_url filter 02-hooks-reference.md
bp_signup_validate jetonomy_check_content filter (content gate) 02-hooks-reference.md

Profile Tabs

In BuddyPress, bp_core_new_nav_item() registers a top-level profile tab; BuddyBoss adds bp_nouveau_get_nav_items() on top.

Jetonomy now has a jetonomy_profile_tabs filter for the same purpose. Full worked example: Add a Profile Tab.

// Register a "Portfolio" tab on every Jetonomy profile.
// Signature: $tabs (array), $user (WP_User), $is_own (bool - true when viewing your own profile).
add_filter( 'jetonomy_profile_tabs', function( array $tabs, \WP_User $user, bool $is_own ): array {
    $tabs[] = [
        'slug'  => 'portfolio',
        'label' => __( 'Portfolio', 'acme' ),
        'url'   => \Jetonomy\get_profile_url( $user->ID ) . 'portfolio/',
    ];
    return $tabs;
}, 10, 3 );

Space Tabs (frontend)

In BuddyPress, BP_Group_Extension adds a frontend tab to a group's sub-navigation. BuddyBoss exposes bp_nouveau_get_group_secondary_nav() on top.

Jetonomy now has a jetonomy_space_tabs filter for the equivalent. Full worked example: Add a Space Tab.

// Add a "Resources" tab to every space's frontend nav.
// Signature: $tabs (array), $space (object), $show_members (bool).
add_filter( 'jetonomy_space_tabs', function( array $tabs, object $space, bool $show_members ): array {
    $tabs[] = [
        'slug'  => 'resources',
        'label' => __( 'Resources', 'acme' ),
    ];
    return $tabs;
}, 10, 3 );

Note: the wp-admin side of space editing uses a different hook pair - jetonomy_admin_space_edit_tabs and jetonomy_admin_space_edit_tab_content. See Admin Extensions.


Trust Levels vs. Member Types

BuddyPress member types (bp_register_member_type) create named cohorts that can gate capabilities, appearance, and directory filters. Jetonomy does not have a member-type registry.

The closest tools are:

  • WP roles - add_role() / add_cap() - for capability gating. Jetonomy's Permission Engine checks WP capabilities via current_user_can() at the community level, then layered space roles and trust levels.
  • Trust Levels 0-5 - auto-evaluated daily from activity (posts, replies, reputation, days active). Readable as $user_profile->trust_level. Listen to jetonomy_trust_level_changed to react to promotions.
  • Space roles - 'member', 'moderator', 'admin' - per-space, not site-wide.

There is no equivalent of BP's per-type directory filter, profile field visibility gating, or per-type nav injection. Those require WP role - based conditionals or trust-level checks in your own code.


What Has No Equivalent (honest gaps)

BuddyPress / BuddyBoss feature Jetonomy status
Site-wide activity stream (bp_activity_*) Not implemented. The BuddyPress integration bridges new Jetonomy posts to a BP group's stream, but Jetonomy has no native activity stream.
Member directory with facet filters Not implemented. jetonomy_search_query_args covers content search; there is no member browser.
Member types (bp_register_member_type) Not implemented. Use WP roles and trust levels.
Member cover images Not implemented.
Notification preference UI hooks Not implemented. The email adapter is swappable, but there is no per-notification-type preference toggle for members.
BP_Group_Extension admin panel Not directly equivalent. Use jetonomy_admin_space_edit_tabs for a wp-admin panel on the space edit page. See Admin Extensions.

These are conscious omissions or open roadmap items, not accidental gaps.


What's Next?

SEO & Discoverability

When Jetonomy is active it adds SEO metadata to every community page automatically: JSON-LD structured data, Open Graph and Twitter cards, canonical URLs, an XML sitemap, and per-space RSS feeds. No separate SEO plugin is needed for the /community/* routes. This page lists what is emitted, where it comes from in the code, and how to change it.

Everything here is in the free plugin unless marked Pro. Jetonomy renders its own /community/* routes from custom tables (there is no WP post object), so general SEO plugins leave these URLs alone - Jetonomy owns their meta and schema.

Structured data (JSON-LD)

On every community page Jetonomy prints a <script type="application/ld+json"> block in wp_head, matched to the page type. Source: includes/seo/class-schema-markup.php.

URL Schema @type Rich-result intent
/community/ (home) WebSite + SearchAction Sitelinks search box
/community/s/{slug}/ (space) CollectionPage + ItemList Itemised topic list
/community/s/{slug}/t/{slug}/ (Q&A topic with an accepted answer) QAPage (Question + Answer + Person) Q&A rich result
/community/s/{slug}/t/{slug}/ (discussion topic) DiscussionForumPosting + InteractionCounter Forum-post rich result with vote + reply counts
/community/tag/{slug}/ CollectionPage + ItemList Itemised tag list
/community/u/{login}/ Person Author/profile
breadcrumbed pages BreadcrumbList Breadcrumb trail

QAPage and DiscussionForumPosting are the two schemas Google specifically documents for forum content, and the InteractionCounter nodes feed the up-vote and answer counts straight from your tables. Validate any page with Google's Rich Results Test.

Document titles

Titles are set through WordPress's document_title_parts filter, so they work with any theme. Two admin settings let owners shape them without code:

Setting (under Jetonomy → Settings → SEO) Applies to Placeholders
seo_post_title single topic pages {post_title}, {site_name}
seo_space_title space pages {space_name}, {site_name}

Leave a pattern empty to use Jetonomy's sensible default. {site_name} is expanded once - the WordPress separator already appends the site name, so you won't get it twice.

Open Graph & Twitter cards

Every community route emits og:title, og:description, og:url, og:image (+ og:image:alt), og:type, and a twitter:card. The image uses a fallback chain so a shared link still carries an image when a page has none of its own:

per-page image  →  seo_default_og_image setting  →  theme custom logo  →  site icon

Set a site-wide default under Jetonomy → Settings → SEO → Default OG image. Descriptions are clipped to 160 characters at emit time.

Canonical URLs

Each page emits a single <link rel="canonical"> pointing at its clean URL, so sort/filter query parameters (e.g. ?sort=top) don't fracture ranking signals across duplicates.

robots / noindex

Thin or duplicative surfaces (such as paginated tails) emit <meta name="robots" content="noindex, follow"> automatically - the links are still crawled, the page just isn't indexed. You can force or clear this per route with the filter below.

XML sitemap

Jetonomy registers two providers on WordPress core's sitemap (wp-sitemap.xml), so spaces and topics are submitted to search engines with no extra plugin (includes/seo/class-sitemap.php):

  • jetonomyspaces - every public space
  • jetonomyposts - every public topic

Visit https://yoursite.com/wp-sitemap.xml and you'll see the jetonomyspaces and jetonomyposts sub-sitemaps listed.

RSS feeds

Every space publishes an RSS 2.0 feed at:

/community/s/{slug}/feed/

The space page also includes an <link rel="alternate" type="application/rss+xml"> tag, so browsers and feed readers auto-discover it. Good for syndication, email-digest tools, Zapier/Make automations, and letting members follow a space without an account.

Customising any of it - the jetonomy_seo_meta filter

One filter sits in front of every meta tag Jetonomy emits. Return a modified payload to change titles, descriptions, canonical, OG image, card type, or noindex per route.

add_filter( 'jetonomy_seo_meta', function ( array $meta, array $data ) {
    // $data['route'] is 'home' | 'space' | 'post' | 'tag' | 'profile' | ...
    // $data['slug']  is the current space/topic/tag/user slug.

    // Example: brand every share image on the leaderboard, and noindex search.
    if ( 'search' === $data['route'] ) {
        $meta['noindex'] = true;
    }
    return $meta;
}, 10, 2 );

The payload you receive and return:

Key Type Controls
title string OG/Twitter title
desc string meta description (clipped to 160 chars on output)
url string canonical + og:url
image string og:image URL
image_alt string og:image:alt
og_type string og:type (default website)
twitter_card string twitter:card (default summary)
noindex bool emit robots noindex, follow
article_meta array article:* meta, keyed by property

Running a general SEO plugin (Yoast, Rank Math)? They no longer collide with Jetonomy's routes. Every Jetonomy URL is virtual - the rewrite sets a jetonomy_route query var that WP_Query doesn't recognise, so core used to fall back to is_home = true. On a site with a static front page that made Yoast publish the Posts page's title and a canonical pointing at it on every Space and topic. Jetonomy now corrects the main query on parse_query - Router::correct_query_state() clears is_home, is_front_page, is_singular, is_404, and the queried object on any jetonomy_route request (includes/class-router.php:106) - so nothing downstream (core's title, themes, breadcrumbs, or an SEO plugin) reads a stale page's data. The fix is vendor-neutral: it corrects the query state rather than fighting any one plugin, so Yoast, Rank Math, AIOSEO, SEOPress and core all resolve correctly with no SEO-plugin code. Jetonomy then sets its own title, canonical, and OG for that route in wp_head. If you'd rather your SEO plugin own a route, return an empty title/desc from this filter for that route to suppress Jetonomy's tags.

On a mapped front page (the community rendered over a real WP page), Jetonomy emits nothing and leaves SEO to the page's own object and your SEO plugin - the query state there is already correct because a real page backs the URL.

SEO Pro (Pro)

The SEO Pro extension adds per-space control on top of the free baseline (jetonomy-pro, extension seo-pro):

  • Per-space meta title, meta description, and OG image
  • Per-space noindex / nofollow and canonical base
  • Per-space sitemap priority and exclude-from-sitemap
  • A robots_txt filter and wp_sitemaps_posts_query_args integration
  • A REST surface (GET/POST per-space SEO) so the controls are editable from the space admin UI

When SEO Pro is active it takes over the topic (post) route's baseline meta to avoid duplicate tags; free continues to own every other route.

Verify it yourself

# Structured data + meta tags on a topic page
curl -s 'https://yoursite.com/community/s/general/t/welcome/' | grep -E 'og:|twitter:|canonical|ld\+json|robots'

# Sitemap providers
curl -s 'https://yoursite.com/wp-sitemap.xml' | grep jetonomy

# A space feed
curl -s 'https://yoursite.com/community/s/general/feed/' | head -20

Models Reference

Data-access models live in includes/models/ and are the only supported way to read or write Jetonomy's custom tables - no raw $wpdb outside includes/db/. This page documents Jetonomy\Models\Attachment.

Attachment

Jetonomy\Models\Attachment - includes/models/class-attachment.php

Links WordPress media items to posts/replies, with ordering and a batch primer so rendering N reply cards issues zero per-row queries. Table: jt_attachments (free-owned since 1.8.0; it's the renamed jt_pro_attachments

  • same schema, no rows copied). Free carries the link so a site that drops Pro still shows its attachments; Pro adds the upload composer, size/type limits, and richer previews on top of the same table.
Attachment::link( string $object_type, int $object_id, int $attachment_id, int $sort = 0 ): int

Attaches a WP media item to a post or reply. Returns the jt_attachments row id. Idempotent - linking the same file to the same object twice is a no-op (backed by a UNIQUE KEY on object_type, object_id, attachment_id), so a resumed or re-run import can't double-attach a file.

get_for()

Attachment::get_for( string $object_type, int $object_id ): array

All link rows for one object, ordered by sort then id. Request-cached per [type][object_id].

get_for_many()

Attachment::get_for_many( string $object_type, int[] $object_ids ): array

Batch-loads attachments for many objects of one type in one query and seeds the per-object cache, keyed by object id (empty array for objects with none). Use when rendering a page of N posts/replies.

prime_for_post()

Attachment::prime_for_post( int $post_id ): void

The N+1 guard for a single-post view: loads attachments for every reply on a post in one query (JOIN jt_replies), so rendering N reply cards costs zero further attachment queries. Called by Attachments::render_post() before the reply loop. Seeds every reply on the post, including ones with no attachments, so a later get_for() call never re-queries.

count_for()

Attachment::count_for( string $object_type, int $object_id ): int

SELECT COUNT(*) for one object. Use for a badge/count display - never count( Attachment::get_for(...) ).

Attachment::unlink( int $link_id ): bool

Removes one link row by its own id (not the media id).

unlink_all()

Attachment::unlink_all( string $object_type, int $object_id ): int

Removes every link row for an object (post/reply cascade delete). Returns the number of rows deleted.

hydrate()

Attachment::hydrate( object $row ): ?array

Turns a jt_attachments row into the canonical attachment shape consumed by both the frontend card renderer and the REST payload - one shape, so the web and the app can never drift. Returns null when the underlying media item was deleted (render nothing rather than a broken card). See the shape table in Hooks Reference - REST payload.

payload_for()

Attachment::payload_for( string $object_type, int $object_id ): array

Hydrates every attachment on an object and runs each through the jetonomy_rest_attachment_data filter. Backs both Attachments::render() and the REST attachments field.

Usage example

use Jetonomy\Models\Attachment;

// Attach an already-uploaded media item to a reply.
$link_id = Attachment::link( 'reply', $reply_id, $media_id, $sort = 0 );

// Render a page of replies without N+1 attachment queries.
Attachment::prime_for_post( $post_id );
foreach ( $replies as $reply ) {
    $attachments = Attachment::get_for( 'reply', $reply->id ); // no extra query
}

Hooks & Filters Reference

Custom hooks Jetonomy fires so themes and extensions (including Jetonomy Pro) can extend behavior without editing plugin files. This page currently covers the attachment seam added in 1.8.0. See includes/class-attachments.php and includes/models/class-attachment.php for the source of truth.

Attachment filters

These three filters are the entire seam between free's basic attachment renderer and Pro's richer one. Pro hooks all three in jetonomy-pro/includes/extensions/attachments/class-extension.php; a site without Pro still shows images inline and other files as download links.

jetonomy_attachment_card

Filters one attachment's card HTML before it's added to the list.

apply_filters( 'jetonomy_attachment_card', string $card, array $attachment, string $object_type, int $object_id ): string
  • $card - free's default <li> markup (image card or file card).
  • $attachment - hydrated attachment array, see Attachment::hydrate().
  • $object_type - 'post' or 'reply'.
  • $object_id - the post or reply id.

Fired once per attachment inside Attachments::render(). Pro returns its own card (lightbox image, inline PDF.js viewer, typed chip) instead of free's.

add_filter( 'jetonomy_attachment_card', function ( $card, $attachment, $object_type, $object_id ) {
    if ( 'pdf' === $attachment['type'] ) {
        return my_render_pdf_card( $attachment );
    }
    return $card;
}, 10, 4 );

jetonomy_attachments_class

Filters the wrapper <ul> class name, default jt-attachments.

apply_filters( 'jetonomy_attachments_class', string $class ): string

Let a richer renderer swap in its own container class so its own CSS targets the strip, without colliding with free's default styling.

jetonomy_rest_attachment_data

Filters one attachment's REST/render data array.

apply_filters( 'jetonomy_rest_attachment_data', array $data, string $object_type, int $object_id ): array

Fired once per row inside Attachment::payload_for() - the same call that backs both the frontend card renderer and the REST payload, so the two can never drift. $data is the shape documented in Attachment::hydrate(). Pro uses this to swap in a gated download URL for non-image files and to add PDF page counts.

REST payload - attachments

As of 1.8.0, jetonomy_rest_prepare_post and jetonomy_rest_prepare_reply (existing REST payload filters) carry an attachments array, injected by Attachments::inject_post_payload() / inject_reply_payload() in includes/class-attachments.php. This is free - a site without Pro still serves attachment data to the mobile app and any REST consumer.

Each item in attachments has this shape:

Key Type Description
id int WordPress media (attachment) ID.
link_id int The jt_attachments row id (use this to unlink).
url string Full-size file URL.
thumb string Thumbnail/medium image URL, or '' for non-images.
mime string MIME type.
name string Real filename with extension.
size int File size in bytes, 0 if unknown.
type string 'image', 'pdf', or 'file'.
ext string Uppercased file extension (e.g. PDF).
is_image bool Convenience flag, true when type === 'image'.

Note the naming reads backwards on purpose: id is the WP media ID, link_id is the jt_attachments row id. That's the shape the mobile app already consumed from Pro before the table moved to free, and is not worth breaking clients over.

Importer Contract

Jetonomy\Import\Importer - includes/import/class-importer.php - is the abstract base every migration source extends. Free ships three: BBPress_Importer, WPForo_Importer, Asgaros_Importer (includes/import/class-{source}-importer.php).

Required methods (abstract)

Every subclass implements:

get_source_name(): string
is_source_available(): bool
get_source_stats(): array
get_total_count(): int
run( array $options = [] ): array
run_batch( string $phase, int $offset, int $batch_size ): array

run_batch() is the batched-import driver: it processes rows for one $phase ('forums'|'topics'|'replies'|'profiles'|'recount') starting at $offset, and returns ['phase', 'offset', 'done', 'processed'] so the caller (AJAX handler or wp jetonomy import) can resume across requests.

Shared helpers on the base class

register_body_media()

register_body_media( string $body, string $path_prefix ): int

Registers every file a post body references, out of the source forum's own upload folder (e.g. wpforo/), into the WP media library - without moving, copying, or rewriting the body. Returns the count of newly tracked files. Use for inline <img>/<a> references left in imported HTML; use link_attachment() below for the attachment box.

ensure_media_id()

ensure_media_id( int $known_media_id, string $file_url, string $file_name = '' ): int

Resolves a source attachment to a WP media ID, importing it if needed. Reuses $known_media_id when the source already registered it. Otherwise resolves $file_url to a real path inside uploads/ via resolve_upload_path(), validates the file type (refuses anything WP wouldn't accept as an upload), and calls wp_insert_attachment(). Returns 0 and logs an error when the file can't be recovered.

resolve_upload_path()

resolve_upload_path( string $file_url ): string

The single place any importer turns a source URL into a real path inside uploads/. Matches on the URL path only (ignores scheme/host), because real forum markup carries protocol-relative URLs (wpForo), stale http://, or an old domain. Enforces containment with realpath() so a crafted ../../../wp-config.php path can never resolve - returns '' for anything outside uploads/ or missing on disk.

link_attachment()

link_attachment( string $object_type, int $object_id, int $attachment_id, int $sort = 0 ): bool

Links a recovered media item to an imported post/reply via Attachment::link(). Always available since attachments moved to free in 1.8.0 - no Pro dependency, no filter round-trip.

sort_rows_parents_first()

sort_rows_parents_first( array $rows, string $id_key, string $parent_key ): array

Reorders source forum rows so every parent is emitted before its children - a breadth-first walk from the roots (parent = 0) outward. Forum hierarchy is preserved by mapping each child's parent_id to its already-imported parent, which only works if the parent was created first; feeding the raw result set straight in dropped any child forum whose parent happened to sort later. Rows whose parent never resolves (orphans or cycles) are appended at the end rather than lost.

Both free importers sort forum rows parents-first before creating spaces so the tree survives the migration, on the one-shot and the batched path: bbPress calls this shared base helper (class-bbpress-importer.php:183 and :449); Asgaros runs an equivalent dependency sort in its own class (class-asgaros-importer.php). Either way parent_id is carried onto the new space.

get_errors()

get_errors(): array

Returns this batch's non-fatal errors as array<int, array{type: string, id: mixed, message: string}> (e.g. an attachment whose file couldn't be recovered). The AJAX handler reads this after every batch and accumulates it into the jetonomy_import_errors option, then the import report renders the count plus a sample of up to 50 rows (includes/admin/ajax/class-import-handler.php, includes/admin/views/import.php) - so a skipped file is reported to the customer instead of vanishing behind a silent "Import complete!".

Per-batch time budget

start_budget(): void   // call at the top of run_batch()
budget_spent(): bool   // check inside the row loop; stop at a row boundary when true

A single 4000×3000 forum photo can cost ~0.9s to register (thumbnail regeneration), so an image-heavy forum can blow past max_execution_time inside one batch. start_budget() sets a deadline (default 15s, filterable):

/**
 * Seconds a single import batch may spend before it stops at a row boundary.
 *
 * @param float $seconds Default 15.
 */
apply_filters( 'jetonomy_import_batch_seconds', 15.0 );

Check budget_spent() between rows and return early (persisting progress) once it trips - a batch that dies mid-request is worse than a slow one: the id map only persists on a completed batch, so a killed batch replays and duplicates already-imported content on resume.

Adding a new source importer

  1. Create includes/import/class-{source}-importer.php, extending Jetonomy\Import\Importer, implementing the abstract methods above.
  2. Use map_id() / get_mapped_id() to track old-ID → new-ID across forums/topics/replies/profiles. If the source nests forums, run the forum rows through sort_rows_parents_first() before creating spaces so a parent is always mapped before its children reference it.
  3. Call start_budget() at the top of run_batch(), and budget_spent() inside the per-row loop.
  4. For inline body media, call register_body_media( $body, 'your-plugin-folder' ). For an explicit attachment box, resolve via ensure_media_id() then link_attachment().
  5. Register the importer in Jetonomy\Import\Import_Manager::init(), or from outside the plugin via the jetonomy_importers filter:
add_filter( 'jetonomy_importers', function ( array $importers ) {
    $importers['my-forum'] = new My_Forum_Importer();
    return $importers;
} );

Caching Reference

Jetonomy\Cache - includes/class-cache.php - is the single object-cache wrapper every model routes through (group jetonomy, default TTL 300s). Do not add per-service wp_cache_* calls or a second cache group; extend this wrapper instead.

API

get() / set() / delete()

Cache::get( string $key )
Cache::set( string $key, $value, int $ttl = 0 ): void   // ttl 0 = DEFAULT_TTL (300s)
Cache::delete( string $key ): void

Thin wrappers over wp_cache_get/set/delete() scoped to the jetonomy group.

delete_many()

Cache::delete_many( string[] $keys ): void

Busts several keys in one call - for writers that must invalidate more than one key whose value changed (e.g. a space row is served under both space:{id} and space:slug:{slug}, so a single write invalidates both).

remember() / remember_object()

Cache::remember( string $key, callable $callback, int $ttl = 0 )
Cache::remember_object( string $key, callable $callback, int $ttl = 0 ): ?object

Standard cache-aside: return the cached value, or call $callback(), cache it, and return it. Use remember_object() for any read with an ?object return contract - some persistent object-cache backends (Redis/Memcached) materialise a cached null as '' on the next read, which remember() alone would hand back as a string and fatal a typed caller. remember_object() coerces any non-object hit back to null.

flush()

Cache::flush(): void

Flushes the whole jetonomy group. Not a per-write path - this is for one-shot admin/CLI/import recomputes whose set-based writes can't name the rows they touched, and the admin "Clear cache" action. Guarded by wp_cache_supports( 'flush_group' ); falls back to a full wp_cache_flush() on a persistent drop-in without group support, and is a no-op with no persistent cache (nothing to flush).

is_persistent()

Cache::is_persistent(): bool

Wraps wp_using_ext_object_cache().

Invalidation rule: bust after the write, in the write method

Every write method busts the exact keys whose value it changed, immediately after the database write completes - never before. Busting first is a re-prime race: a concurrent read between the bust and the write re-caches the stale value, and it stays stale for the full TTL.

A set-based UPDATE ... WHERE id IN (...) cannot invalidate what it doesn't name - bust each affected id explicitly, or call Cache::flush() for a one-shot admin/CLI/import path that can't enumerate the rows cheaply.

Member-visible state a user just changed (their own post count, a space they just made private) must never rely on TTL expiry alone - bust the exact key.

Reference pattern: Space::bust_cache()

includes/models/class-space.php:

/**
 * Bust every object-cache key that serves a space row: the id key and,
 * when given, the slug->id mapping key. Row data lives once, under
 * space:{id}; find_by_slug() caches only the stable slug->id mapping under
 * space:slug:{slug}. Callers bust AFTER the DB write.
 */
public static function bust_cache( int $id, ?string $slug = null ): void {
    $keys = [ "space:{$id}" ];
    if ( ! empty( $slug ) ) {
        $keys[] = "space:slug:{$slug}";
    }
    Cache::delete_many( $keys );
}

Callers invoke it after every write that changes a space row: update() (old + new slug on rename), the hard delete() override, and both counter increments (id-only - the slug mapping doesn't change on a count bump). This is the model to follow for any new cached row: one bust_cache( $id, ...) helper on the model, called from every writer, after the write, never a listener on a do_action (a listener misses any caller that doesn't fire the action - REST, AJAX, CLI, import, and Abilities callers all mutate a space, and only busting inside the model methods catches all of them).

public static function update( int $id, array $data ): bool {
    $old_slug = ( parent::find( $id )->slug ?? null );
    $result   = parent::update( $id, $data );
    self::bust_cache( $id, $old_slug );
    if ( ! empty( $data['slug'] ) && $data['slug'] !== $old_slug ) {
        Cache::delete( "space:slug:{$data['slug']}" ); // rename invalidates the new key too
    }
    return $result;
}

Media Provenance

Jetonomy\Media_Library - includes/class-media-library.php - hides community uploads from the admin Media Library by default (member uploads go through the standard WP media library so third-party offload/compression plugins keep working transparently) and tracks which uploads Jetonomy itself created, so Pro's garbage collector never deletes another plugin's file.

The provenance problem

META_FLAG (_jetonomy_media) marks "this looks like a community upload" - including files a one-shot backfill merely inferred (any attachment whose author lacks upload_files). That heuristic is true of every subscriber-authored attachment on the site, including another forum plugin's - wpForo, for example, inserts its own attachments authored by the posting member. META_FLAG alone is fine for hiding a file from the admin grid; it is not safe for deleting it.

META_ORIGIN

const META_ORIGIN = '_jetonomy_media_origin';

Set only when Jetonomy itself created the upload - recorded at write time, never inferred afterward. This is the sole flag Pro's daily GC checks before force-deleting an unlinked attachment. Without it, enabling attachments mid-migration could destroy the very files an import exists to rescue.

tag_upload()

Media_Library::tag_upload( int $attachment_id, int $space_id = 0 ): void

Call this from the code path that creates the upload (the REST media controller, at upload time). Sets META_FLAG (hide from admin grid), META_ORIGIN = 'upload' (GC-eligible), and META_SPACE when a space id is known. Static and safe to call outside is_admin().

is_ours()

Media_Library::is_ours( int $attachment_id ): bool

Returns true only when META_ORIGIN === 'upload'. This is the gate any delete/GC routine must check before removing a file - a capability check or META_FLAG presence is not sufficient. Pro's attachment GC cron (gc() in jetonomy-pro/includes/extensions/attachments/class-extension.php) calls this immediately before wp_delete_attachment( $id, true ).

Usage example

use Jetonomy\Media_Library;

// After a member uploads a file via your own code path:
Media_Library::tag_upload( $attachment_id, $space_id );

// Before any code deletes an unused/orphaned attachment:
if ( Media_Library::is_ours( $attachment_id ) ) {
    wp_delete_attachment( $attachment_id, true );
}

Never assume an attachment is safe to delete because it's unused and "looks like" a community upload - check is_ours() first. Only files Jetonomy itself uploaded are GC-eligible; anything merely recognized (another plugin's media, an older member upload the backfill flagged) is someone else's file, and being unused isn't a good enough reason to delete it.

Jetonomy REST API Reference

Generated from the Jetonomy 1.8.0 (free) source by reading every register_rest_route() call in includes/api/*.php, and from Jetonomy Pro's includes/extensions/*/class-*.php. This is the human-readable companion to openapi.json - the OpenAPI 3.1 file is the authoritative, machine-readable contract (exact schemas, every parameter's type/enum/default). Load openapi.json into Swagger UI or Redoc for an interactive browser.

Base URLs

Namespace Base URL Used by
jetonomy/v1 https://your-site.com/wp-json/jetonomy/v1 Free core, and most Pro extensions (Private Messaging, Analytics, Polls, Reactions, Custom Fields, Custom Badges, Webhooks, White Label, Web Push, AI, Advanced Moderation, Email Digest, SEO Pro, Reply By Email, Anonymous Posting, and the public GET /announcements/active read)
jetonomy-pro/v1 https://your-site.com/wp-json/jetonomy-pro/v1 Pro's Attachments extension (/attachments*), and the Site Announcements admin/management routes (/site-announcements, /site-announcements/{id})

The jetonomy-pro/v1 split is not documented anywhere in the plugin's own manifest notes - it was found by reading includes/extensions/attachments/class-rest.php and includes/extensions/site-announcements/class-extension.php directly. Every other Pro extension registers into the shared jetonomy/v1 namespace.

Authentication

Jetonomy supports two authentication modes, both handled by \Jetonomy\API\REST_Auth::auth_mutation() (free) and its lazy Pro wrapper Extension::rest_auth_mutation():

The browser holds the WordPress logged_in session cookie. Every mutating request (POST/PATCH/DELETE) additionally requires an X-WP-Nonce header carrying a valid wp_rest nonce:

X-WP-Nonce: <nonce>

Get one from wp_create_nonce('wp_rest') server-side (localized into the page on load), or refresh it client-side via:

GET /wp-json/jetonomy/v1/auth/nonce

A missing/invalid nonce on a cookie-authenticated request returns 403 rest_cookie_invalid_nonce. Cookie-authenticated requests are detected by the presence of $_COOKIE and the absence of an Authorization header - see the next section.

2. Application Passwords (the mobile app + external integrations)

WordPress core Application Passwords sent as HTTP Basic auth:

Authorization: Basic base64(username:application_password)

Header-authenticated requests skip the cookie-nonce check entirely (Application Passwords authenticate per-request and don't carry a WP session). They still go through the same login, ban/silence, and pending-email-verification gates as cookie auth - REST_Auth::auth_mutation() is the single choke point for both.

Account-status gates (both modes)

On every mutation route, auth_mutation() also enforces:

  • Banned accounts are rejected (403 jetonomy_user_banned) - closes an Application Password bypass that existed before 1.6.0, where a banned member's authenticate filter never ran for header-authed requests.
  • Pending-email-verification accounts are rejected (403 jetonomy_pending_verification) unless the route explicitly opts out (allow_unverified) - currently only DELETE /users/me (a member must always be able to delete an account they created, verified or not).

Public / private community mode

Independent of the above, Settings → General → Community Access toggles the whole community between public (default - anyone can read) and private (every read requires login). This is enforced by \Jetonomy\Visibility::rest_check() and wraps almost every GET route's permission_callback. In private mode, a logged-out GET returns 401 community_private instead of the resource. /auth/* and most /moderation/*//admin/* routes are exempt (they have their own gates and must never lock users out).

Rate limiting

POST /auth/login, /auth/register, /auth/lost-password, /auth/resend-verification, and DELETE /users/me are all IP-bucketed via a shared Base_Controller::check_rate_limit() transient (login: 5/min; register: 5/min; lost-password & resend-verification: 3/5min; delete-account: 5/hour). Exceeded limits return 429 jetonomy_rate_limited.

Pagination

Two shapes appear across the API, both bounded (no unbounded SELECT *):

  • Offset-based (limit + offset, most list endpoints): response body is { "data": [...], "meta": { "count", "has_more", "cursor_next", "total", "offset" } }. X-WP-Total and X-WP-TotalPages headers are set on most collection routes.
  • Cursor-based (after/before on posts/replies/messages): the base get_collection_params() schema declares after/before alongside legacy offset - cursor is preferred for large datasets since OFFSET on a 100k-row table degrades linearly.

Default limit is usually 20 (search defaults 20, capped 50; leaderboards default 20, capped 100; media default 24). Always check the specific endpoint's args schema in openapi.json for the real min/max/default.

Error shape

Every error is a standard WP_Error-shaped JSON body:

{ "code": "jetonomy_forbidden", "message": "You do not have permission to perform this action.", "data": { "status": 403 } }

Resources

Spaces

Sub-communities. POST /spaces requires manage_options OR jetonomy_create_spaces OR a WP role the admin opted into (Settings → General → frontend_space_creation_roles). Space-level writes (PATCH/DELETE on a space, member-role changes, invites) require the caller to hold the admin per-space role (independent of WP capabilities) or manage_options.

GET    /spaces                                          List (visibility-filtered)
POST   /spaces                                           Create
GET    /spaces/{id}                                       Get
PATCH  /spaces/{id}                                        Update (space admin)
DELETE /spaces/{id}                                        Delete (space admin)
GET    /spaces/{id}/members                                List members
POST   /spaces/{id}/members                                Join
GET    /spaces/{id}/privileged-members                     Admins/moderators (public read)
GET    /spaces/{id}/join-requests                          Pending requests (space mod)
POST   /spaces/{id}/join-requests/{request_id}/approve      Approve
POST   /spaces/{id}/join-requests/{request_id}/deny         Deny
PATCH  /spaces/{id}/members/{user_id}                      Change role (space admin)
DELETE /spaces/{id}/members/{user_id}                       Leave/remove
POST   /spaces/{id}/invite                                  Generate invite link (space admin)
GET    /invite/{token}                                     Accept invite

Example - create a space:

curl -X POST https://your-site.com/wp-json/jetonomy/v1/spaces \
  -H "Content-Type: application/json" -H "X-WP-Nonce: $NONCE" --cookie "$COOKIES" \
  -d '{"title":"Product Feedback","type":"ideas","visibility":"public","join_policy":"open"}'
{ "id": 42, "title": "Product Feedback", "slug": "product-feedback", "type": "ideas", "visibility": "public", "join_policy": "open", "member_count": 1, "post_count": 0, "is_member": true, "viewer_role": "admin", "..." : "..." }

Posts

Topics/questions/ideas/status-updates, always nested under a space for create/list. 1.8.0 adds an attachments array to every post payload (free, whether or not Pro is active - see below).

GET    /spaces/{space_id}/posts    List (space-scoped, sort=latest|popular|oldest|newest|unanswered)
POST   /spaces/{space_id}/posts    Create
GET    /posts/drafts               My drafts
GET    /posts/{id}                 Get
PATCH  /posts/{id}                  Update / publish a draft
DELETE /posts/{id}                  Trash
POST   /posts/{id}/close            Close/reopen to new replies
POST   /posts/{id}/pin              Pin/unpin (capped per space)
POST   /posts/{id}/move             Move to another space
POST   /posts/{id}/merge            Merge into another post
POST   /posts/{id}/idea-status      Set roadmap status (Ideas spaces only)
POST   /posts/{id}/vote             Vote
DELETE /posts/{id}/vote              Remove my vote
GET    /link-preview                OG metadata for a URL

Example response fragment (GET /posts/{id}), showing the 1.8.0 attachments shape:

{
  "id": 501, "title": "How do I configure SSO?", "type": "question", "status": "publish",
  "content": "<p>...</p>", "viewer_vote": 0, "is_bookmarked": false,
  "attachments": [
    { "id": 88, "link_id": 12, "url": "https://.../screenshot.png", "thumb": "https://.../screenshot-300x200.png",
      "mime": "image/png", "name": "screenshot.png", "size": 84213, "type": "image", "ext": "PNG", "is_image": true }
  ]
}

Replies

Threaded, up to 3 levels via parent_id, nested under a post for create/list.

GET    /posts/{post_id}/replies    List (sort=oldest|newest|best)
POST   /posts/{post_id}/replies    Create
PATCH  /replies/{id}               Update (no GET on this route)
DELETE /replies/{id}               Trash
POST   /replies/{id}/accept        Mark accepted answer (Q&A spaces)
DELETE /replies/{id}/accept        Un-accept
POST   /replies/{id}/split         Split into a new post (moderator)
POST   /replies/{id}/vote          Vote
DELETE /replies/{id}/vote           Remove my vote

Users

GET    /users/me                   My full profile
PATCH  /users/me                    Update my profile
DELETE /users/me                    Delete my account (Apple 5.1.1(v) / GDPR Art. 17)
GET    /users/{id}                 Public profile by ID
GET    /users/by-login/{login}     Public profile by username
GET    /users/{id}/posts           A user's public posts
GET    /users/suggest              @mention typeahead (login required)

DELETE /users/me example:

curl -X DELETE https://your-site.com/wp-json/jetonomy/v1/users/me \
  -H "Content-Type: application/json" -H "X-WP-Nonce: $NONCE" --cookie "$COOKIES" \
  -d '{"password":"correct horse battery staple","confirm":"DELETE","delete_content":false}'
{ "deleted": true, "user_id": 88, "content_policy": "anonymized" }

By default content is anonymized (author reassigned to a 0/tombstone; existing replies still render, showing [deleted]). Pass delete_content: true to hard-delete every post/reply the member authored instead.

Blocks (member-to-member blocking)

GET    /users/me/blocks              List who I've blocked
POST   /users/me/blocks              Block a user ({user_id})
DELETE /users/me/blocks/{user_id}     Unblock (idempotent)

Auth

POST /auth/login                 Sign in (rate-limited)
POST /auth/register              Create an account (rate-limited, anti-spam)
POST /auth/lost-password         Request a reset email (rate-limited)
GET  /auth/verify-email          Consume an email verification link
GET  /auth/nonce                 Fresh wp_rest nonce for this session
POST /auth/resend-verification   Resend the verification link (rate-limited)

Notifications

GET    /notifications                     List (filter=all|unread|mentions|replies|votes|badges)
GET    /notifications/unread-count        Unread count (cached 15s)
POST   /notifications/mark-all-read       Mark all read
PATCH  /notifications/{id}                 Mark one read
DELETE /notifications/{id}                  Delete one (scoped to me)
POST   /notifications/bulk                Bulk mark-read/delete ({action, ids})

Subscriptions

GET    /subscriptions          List mine (now includes title/slug/exists of the target, 1.8.0)
POST   /subscriptions          Subscribe to a space or post ({object_type, object_id, via})
DELETE /subscriptions/{id}      Unsubscribe
GET /search?q=...&type=post|reply|space|tag|all&sort=relevance|newest|votes&limit=&offset=

MySQL FULLTEXT boolean-mode search. type=all returns {posts, spaces, tags} grouped, with limit/offset applying to the posts group only. 1.7.1 added real limit/offset pagination (previously hardcoded to 20/10 per type) plus X-WP-Total/X-WP-TotalPages headers.

Categories, Tags, Leaderboards, Bookmarks

GET/POST     /categories                 List (nested) / create (jetonomy_manage_categories)
GET/PATCH/DELETE /categories/{id}        Get / update / delete (jetonomy_manage_categories)
GET          /tags                       List post tags (GET only - no write routes)
GET          /leaderboards               Reputation ranking (period=all|month|week)
GET/POST     /bookmarks                  List mine / toggle a bookmark
DELETE       /bookmarks/{post_id}         Remove

Moderation

GET    /moderation/queue                          Pending+spam queue (jetonomy_moderate)
POST   /moderation/approve/{type}/{id}             Approve
POST   /moderation/spam/{type}/{id}                Mark spam (reputation penalty)
POST   /moderation/trash/{type}/{id}               Trash
GET/POST /flags                                    List (mod) / report content
GET    /moderation/flags                           List flags for moderation
POST   /moderation/flags/{id}/resolve              Resolve (valid trashes + rewards reporter)
POST   /moderation/bulk                            Bulk approve/spam/trash
GET    /posts/{id}/flags                           Flags on one post
GET/POST /moderation/ban                           List / issue ban-silence-spaceban
DELETE /moderation/ban/{id}                        Lift a restriction
GET    /spaces/{id}/moderation/flags               Space-scoped flag queue (space mod)
POST   /spaces/{id}/moderation/flags/{flag_id}/resolve
POST   /spaces/{id}/moderation/{approve|spam|trash}/{type}/{obj_id}

Banning has three built-in integrity guards: nobody can restrict themselves, nobody can restrict a site administrator, and a moderator (not an admin) cannot restrict another moderator.

Media

POST /media    Upload an image (multipart, upload_files|jetonomy_upload_media|jetonomy_create_posts|jetonomy_create_replies)
GET  /media    List community media library uploads (jetonomy_manage_settings)

Updates, OEmbed, Admin

GET  /updates    Poll for activity since a timestamp (scope=global|space|post)
GET  /oembed     oEmbed 1.0 JSON for a forum thread URL (Slack/X/Discord unfurl)
POST /admin/recount               Rebuild counters (manage_options)
POST /admin/users/trust-level     Bulk-set trust level (manage_options)

App / Feed (mobile app surface)

GET /app/config    Public branding + Pro feature flags (pre-login theming)
GET /feed          Global cross-space home feed (sort=hot|new|top)

Pro Resources (jetonomy/v1 unless noted)

Private Messaging

GET/POST     /conversations                          List mine / start a new one
GET/PATCH    /conversations/{id}                      Get / mute
GET/POST     /conversations/{id}/messages             List / send (cursor: before)
GET          /conversations/unread-count              Cached 30s
POST         /conversations/{id}/mute                 Toggle mute
POST         /conversations/{id}/archive              Toggle archive
POST         /conversations/{id}/leave                Leave (group)
POST         /conversations/{id}/block                Block the other side (direct)
GET          /messaging/recipient-suggestions          Typeahead (shared-space scoped)

Requires trust level ≥ 1 to send (site admins/moderators bypass). A new direct message additionally requires the sender and recipient to share at least one space, and neither side may have blocked the other.

Reactions

GET/POST /posts/{id}/reactions      Get counts / toggle ({emoji})
GET/POST /replies/{id}/reactions    Get counts / toggle

Single-reaction-per-user model - re-adding a different emoji swaps the prior one. POST requires jetonomy_vote.

Polls

GET/POST /posts/{post_id}/poll    Get / create ({question, type, options[], closes_at})
PATCH    /polls/{id}              Close/reopen (poll creator or moderator)
POST     /polls/{id}/vote         Cast/switch vote
DELETE   /polls/{id}/vote          Remove my vote

One poll per post (409 on a second create). Multiple-choice POST /polls/{id}/vote treats option_ids as the desired final set (idempotent reconcile), not a per-option toggle.

Custom Fields

GET/POST         /fields                    List (public) / create (manage_options)
PATCH/DELETE      /fields/{id}               Update / deactivate (manage_options)
GET/PATCH        /posts/{id}/fields          Read (public) / write (author/mod)
GET              /users/{id}/fields          Read
PATCH            /users/me/fields            Write my own

Field types: text, textarea, number, email, url, select, checkbox, radio, date. Contexts: post, profile, space. Write endpoints accept a raw JSON object of {slug: value} and validate/sanitize per field type; invalid values are reported per-field in a 400 without blocking the rest of the save.

Custom Badges

GET/POST          /badges                  List active (paginated) / create (manage_options)
GET/PATCH/DELETE  /badges/{id}              Get (with earned_count) / update / deactivate
GET               /users/{id}/badges        A user's earned badges
POST/DELETE       /badges/{id}/award        Manual award / revoke ({user_id})

Auto-award runs on an event-driven queue (post/reply/vote/reputation/trust-level changes) plus a 6-hour cron sweep. Criteria: 8 metrics (post_count, reply_count, reputation, trust_level, vote_received, days_active, accepted_answers, spaces_joined) × 5 operators × all/any match mode.

Webhooks

GET/POST         /webhooks                  List / create (manage_options)
PATCH/DELETE      /webhooks/{id}             Update / delete
POST             /webhooks/{id}/test         Send a test delivery
GET              /webhooks/{id}/deliveries   Paginated delivery log (30-day retention)

13 event slugs: post.created/updated/deleted, reply.created/updated/deleted, user.registered, user.trust_level_changed, vote.cast, flag.created/resolved, space.member_joined/left. Payloads are HMAC-signed (X-Jetonomy-Signature: sha256=...), event name in X-Jetonomy-Event. Auto-disabled after 5 consecutive delivery failures.

White Label

GET/PATCH /settings/white-label    Branding (manage_options)

Site Announcements

GET  /jetonomy/v1/announcements/active                 Public/app read of active pins
GET  /jetonomy-pro/v1/site-announcements                Admin: list pinned post IDs (manage_options)
POST /jetonomy-pro/v1/site-announcements/{id}            Admin: pin a post (capped at 5)
DELETE /jetonomy-pro/v1/site-announcements/{id}           Admin: unpin

Note the namespace split - the public read stays on the shared jetonomy/v1 namespace; the admin write surface is on jetonomy-pro/v1.

Web Push

GET          /push/vapid-key              Public VAPID key
GET          /push/service-worker.js      Service worker script
POST/DELETE  /push/subscribe              Browser PushSubscription
POST/DELETE  /push/register-device        Native (Expo) push token

AI

GET  /ai/usage             Usage stats (manage_options)
GET  /ai/usage/summary     By-provider summary (manage_options)
POST /ai/suggest-reply     Suggest a reply draft ({post_id}, logged in + can read the post)

Advanced Moderation

GET/POST         /moderation/rules             List / create rules (manage_options)
PATCH/DELETE      /moderation/rules/{id}        Update / delete
GET              /moderation/rules/{id}/stats   Trigger stats

Email Digest

GET/PATCH /users/me/digest-preferences    My frequency + subscribed types
POST      /admin/digest/test              Send a test digest to me (manage_options)
GET       /admin/digest/stats             Send statistics (manage_options)

SEO Pro

GET/PATCH /spaces/{id}/seo    Per-space meta title/description/OG image (space admin)

Reply By Email

POST /reply-by-email/inbound    Inbound email webhook - signature/token-verified, not a WP capability

Anonymous Posting

POST /anonymous/reveal    Reveal the real author of an anonymous post/reply (manage_options, audit-logged)

Attachments - jetonomy-pro/v1 namespace

POST   /attachments               Link an uploaded WP media item to a post/reply
DELETE /attachments/{id}           Detach a link (id = link_id, not the WP media ID)
GET    /attachments/{id}/download  Force-download (Content-Disposition, public read following the parent's visibility)
GET    /attachments/batch          Batch read for an admin content-view page

The attachment link storage and REST payload injection are free as of 1.7.1/1.8.0 - Jetonomy\Models\Attachment and the attachments array on Post/Reply responses work with or without Pro. Pro's attachments extension adds the upload composer UI, size/type policy, and the /jetonomy-pro/v1/attachments* link/detach/download management routes.


Changelog note (1.8.0)

  • Post and reply REST payloads now always carry an attachments array (free).
  • POST /users/me/blocks, DELETE /users/me/blocks/{user_id} - block/report a member.
  • DELETE /users/me - in-app account deletion.
  • Object cache is invalidated immediately on space/profile/membership writes.
  • GET /subscriptions now returns title/slug/exists for each subscribed target.
  • GET /search gained real limit/offset pagination and X-WP-Total/X-WP-TotalPages headers (previously hardcoded per-type limits).

See openapi.json for the exact parameter/response contract of every route above.

Something unclear? Open a support ticket →

Buy Jetonomy