Programmatic Usage

NotedWP exposes several public PHP classes you can use directly in your theme or plugin code. This page covers the most useful methods for license checks, plan-gated features, limit enforcement, and custom capabilities.

Always wrap NotedWP calls in a class existence check to prevent fatal errors if the plugin is deactivated:

if (!class_exists('Noted_License')) {
    return;
}

Noted_License

The Noted_License class handles plan detection and feature gating. All methods are static.

Noted_License::get_plan()

Returns the active plan as a string.

Returns: string (one of 'free', 'pro', 'agency', or 'm13_client')

$plan = Noted_License::get_plan();
// "free", "pro", "agency", or "m13_client"

Plan resolution order:

  1. If an M13 client token is present (noted_m13_client_token option), the plan resolves to m13_client.
  2. If a valid license is active (noted_license_status is active), the plan resolves to the stored tier (pro or agency).
  3. Otherwise, falls back to free.

Noted_License::can(string $feature)

Check whether the current plan includes a specific feature.

Parameters:

  • $feature (string) – Feature key to check.

Returns: bool

if (Noted_License::can('drawing_tools')) {
    // Enable annotation toolbar.
}

if (Noted_License::can('export_integrations')) {
    // Show Trello/Asana/Jira export options.
}

Available feature keys:

Feature Key Free Pro Agency
all_pages No Yes Yes
unlimited_pins No Yes Yes
unlimited_guests No Yes Yes
drawing_tools No Yes Yes
text_edits No Yes Yes
priority_labels No Yes Yes
internal_pins No Yes Yes
bulk_resolve No Yes Yes
password_links No Yes Yes
link_expiry No Yes Yes
keyboard_shortcuts No Yes Yes
export_csv Yes Yes Yes
export_integrations No Yes Yes
export_task_mgmt No Yes Yes
export_m13 No Yes Yes
export_advanced No No Yes
export_webhook No No Yes
custom_branding No Yes Yes
password_protect No Yes Yes
expiry_links No Yes Yes
white_label No No Yes
multisite No No Yes

The m13_client plan has the same features as agency.


Noted_License::is_tier(string $tier)

Check if the current plan matches a specific tier exactly.

Parameters:

  • $tier (string) – One of 'free', 'pro', 'agency', 'm13_client'.

Returns: bool

if (Noted_License::is_tier('agency')) {
    // Show agency-only branding options.
}

Noted_License::is_at_least(string $min_plan)

Check if the current plan meets or exceeds a minimum tier. The hierarchy is: free (0) < pro (1) < agency (2). The m13_client plan is treated as equivalent to agency (tier 2).

Parameters:

  • $min_plan (string) – Minimum plan required.

Returns: bool

if (Noted_License::is_at_least('pro')) {
    // Any paid plan (pro, agency, m13_client) passes this check.
}

Noted_License::is_m13_managed()

Check if the site is managed by Mountain Thirteen Media via the M13 client token system.

Returns: bool

if (Noted_License::is_m13_managed()) {
    // Adjust UI for M13-managed sites.
}

Noted_Limits

The Noted_Limits class enforces free-tier restrictions. On paid plans, these methods generally return true or null (unlimited). All methods are static.

Constants

Noted_Limits::FREE_PIN_LIMIT   // 25
Noted_Limits::FREE_GUEST_LIMIT // 3

Noted_Limits::can_review_page(string $path)

Check if the overlay should load on a given page path. On the free tier, only the homepage (/) is allowed. Pro and Agency plans allow all pages.

Parameters:

  • $path (string) – The relative URL path (e.g., /about/).

Returns: bool

$path = wp_make_link_relative(get_permalink());

if (Noted_Limits::can_review_page($path)) {
    // Overlay will load on this page.
}

Noted_Limits::can_create_pin()

Check if the site has room for more pins. Free tier is capped at 25 total pins. Paid plans are unlimited.

Returns: bool

if (!Noted_Limits::can_create_pin()) {
    wp_die('Pin limit reached. Upgrade to Pro for unlimited pins.');
}

Noted_Limits::can_add_guest(int $project_id)

Check if a project can accept more guest reviewers. Free tier is capped at 3 guests per project.

Parameters:

  • $project_id (int) – The project ID.

Returns: bool

if (Noted_Limits::can_add_guest($project_id)) {
    // Allow guest registration.
} else {
    // Show upgrade prompt.
}

Noted_Limits::get_remaining_pins()

Get the number of pins remaining before hitting the free tier cap. Returns null on paid plans (unlimited).

Returns: ?int

$remaining = Noted_Limits::get_remaining_pins();

if ($remaining === null) {
    echo 'Unlimited pins available.';
} else {
    echo "{$remaining} pins remaining on free plan.";
}

Noted_Limits::get_remaining_guests(int $project_id)

Get the number of guest slots remaining for a project on the free tier. Returns null on paid plans.

Parameters:

  • $project_id (int) – The project ID.

Returns: ?int

$remaining = Noted_Limits::get_remaining_guests($project_id);

Noted_Limits::can_use_feature(string $feature)

Convenience wrapper that calls Noted_License::can($feature) internally.

Parameters:

  • $feature (string) – Feature key (see the feature table above).

Returns: bool

if (Noted_Limits::can_use_feature('text_edits')) {
    // Enable the text edit tool in your custom UI.
}

Noted_Capabilities

The Noted_Capabilities class defines the four custom WordPress capabilities used by NotedWP. These are string constants you can reference when checking permissions.

Constants

Noted_Capabilities::MANAGE_REVIEWS  // 'noted_manage_reviews'
Noted_Capabilities::VIEW_REVIEWS    // 'noted_view_reviews'
Noted_Capabilities::RESOLVE_PINS    // 'noted_resolve_pins'
Noted_Capabilities::EXPORT_REVIEWS  // 'noted_export_reviews'

Default Role Assignments

Capability Administrator Editor
noted_manage_reviews Yes No
noted_view_reviews Yes Yes
noted_resolve_pins Yes Yes
noted_export_reviews Yes No

Granting Capabilities to Other Roles

You can grant NotedWP capabilities to additional roles (e.g., author) using standard WordPress functions:

add_action('admin_init', function () {
    $author = get_role('author');
    if ($author) {
        $author->add_cap(Noted_Capabilities::VIEW_REVIEWS);
        $author->add_cap(Noted_Capabilities::RESOLVE_PINS);
    }
});

Important. add_cap() writes to the database. Run this once (e.g., on plugin activation or via a one-time admin action), not on every page load.

Checking Capabilities

Use current_user_can() with the constants:

if (current_user_can(Noted_Capabilities::MANAGE_REVIEWS)) {
    // User can manage pins, settings, and review data.
}

if (current_user_can(Noted_Capabilities::EXPORT_REVIEWS)) {
    // User can export pins to external services.
}

Practical Examples

Gate a Feature Behind Plan Level

Show or hide a custom settings section based on the active NotedWP plan:

add_action('admin_menu', function () {
    if (!class_exists('Noted_License')) {
        return;
    }

    if (Noted_License::is_at_least('pro')) {
        add_submenu_page(
            'noted',
            'Advanced Feedback Settings',
            'Advanced',
            Noted_Capabilities::MANAGE_REVIEWS,
            'noted-advanced',
            'render_advanced_settings_page'
        );
    }
});

Send a Custom Email When a Pin is Created

add_action('noted_pin_created', function (int $pin_id, array $pin_data) {
    if (!class_exists('Noted_License')) {
        return;
    }

    $page_url  = home_url($pin_data['page_path'] ?? '/');
    $admin_url = admin_url("admin.php?page=noted-pins&action=view&pin={$pin_id}");

    $subject = sprintf('[NotedWP] New pin #%d on %s', $pin_data['pin_number'] ?? $pin_id, $page_url);
    $body    = sprintf(
        "A new feedback pin was placed on %s.nnComment: %snnPriority: %snBreakpoint: %snnView in admin: %s",
        $page_url,
        $pin_data['body'] ?? '(no comment)',
        $pin_data['priority'] ?? 'normal',
        $pin_data['breakpoint'] ?? 'desktop',
        $admin_url
    );

    wp_mail('[email protected]', $subject, $body);
}, 10, 2);

Query Pins via $wpdb for a Custom Report

Build a summary of open feedback grouped by page:

function get_noted_feedback_summary(): array {
    global $wpdb;
    $prefix = $wpdb->prefix;

    $results = $wpdb->get_results(
        "SELECT
            p.url_path,
            p.title,
            COUNT(CASE WHEN pin.status = 'open' THEN 1 END) AS open_count,
            COUNT(CASE WHEN pin.status = 'resolved' THEN 1 END) AS resolved_count,
            MAX(pin.created_at) AS latest_pin
        FROM {$prefix}noted_pages p
        LEFT JOIN {$prefix}noted_pins pin ON pin.page_id = p.id
        GROUP BY p.id
        ORDER BY open_count DESC",
        ARRAY_A
    );

    return $results;
}

// Usage:
$summary = get_noted_feedback_summary();
foreach ($summary as $row) {
    printf(
        "%s: %d open, %d resolvedn",
        $row['title'] ?? $row['url_path'],
        $row['open_count'],
        $row['resolved_count']
    );
}

Display a Pin Count Badge in Your Theme

Show the number of open feedback items for the current page:

function noted_pin_badge(): string {
    if (!class_exists('Noted_Capabilities') || !current_user_can(Noted_Capabilities::VIEW_REVIEWS)) {
        return '';
    }

    global $wpdb;
    $path = wp_make_link_relative(get_permalink());

    $count = (int) $wpdb->get_var($wpdb->prepare(
        "SELECT COUNT(*)
        FROM {$wpdb->prefix}noted_pins pin
        INNER JOIN {$wpdb->prefix}noted_pages page ON page.id = pin.page_id
        WHERE page.url_path = %s AND pin.status = 'open'",
        $path
    ));

    if ($count === 0) {
        return '';
    }

    return sprintf(
        '<span class="noted-badge" style="background:#E06042;color:#fff;padding:2px 8px;border-radius:12px;font-size:12px;">%d open</span>',
        $count
    );
}

Use it in a template:

<h1><?php the_title(); ?> <?php echo noted_pin_badge(); ?></h1>

Check Limits Before Custom Pin Creation

If you are programmatically creating pins (outside of the REST API), check limits first:

function my_custom_create_pin(array $pin_data): ?int {
    if (!class_exists('Noted_Limits')) {
        return null;
    }

    if (!Noted_Limits::can_create_pin()) {
        error_log('NotedWP pin limit reached.');
        return null;
    }

    global $wpdb;
    $wpdb->insert($wpdb->prefix . 'noted_pins', [
        'page_id'     => $pin_data['page_id'],
        'project_id'  => $pin_data['project_id'],
        'x_percent'   => $pin_data['x_percent'],
        'y_percent'   => $pin_data['y_percent'],
        'pin_number'  => $pin_data['pin_number'],
        'status'      => 'open',
        'priority'    => $pin_data['priority'] ?? 'normal',
        'author_type' => 'user',
        'author_user_id' => get_current_user_id(),
        'body'        => $pin_data['body'] ?? '',
        'breakpoint'  => $pin_data['breakpoint'] ?? 'desktop',
    ]);

    $pin_id = (int) $wpdb->insert_id;

    // Fire the same hook the REST API fires, so notifications still work.
    do_action('noted_pin_created', $pin_id, $pin_data);

    return $pin_id;
}

Note. When creating pins programmatically, remember to fire noted_pin_created yourself. The hook only fires automatically from the REST API. The same applies to noted_comment_created and noted_pin_resolved.