---
title: "Permissions & Roles - PerfLocale"
description: "PerfLocale permissions system with the custom Translator role and granular capabilities for translating posts, managing strings, and running migrations."
canonical: "https://perflocale.com/docs/permissions/"
source: "https://perflocale.com/docs/permissions/"
format: "markdown"
---
# Permissions & Roles

PerfLocale provides a comprehensive permissions system with a custom **Translator** role and granular capabilities that integrate with WordPress's built-in role system.

## Custom Role: Translator

**Slug:** `perflocale_translator`

The Translator role is designed for team members who only need to translate content. They can edit posts and pages (to translate them) but cannot publish, delete, or manage site settings.

### WordPress capabilities included:

| Capability | Granted |
| --- | --- |
| read | Yes |
| edit_posts | Yes |
| edit_others_posts | Yes |
| edit_published_posts | Yes |
| edit_pages | Yes |
| edit_others_pages | Yes |
| edit_published_pages | Yes |
| upload_files | Yes |
| publish_posts | No |
| delete_posts | No |
| manage_options | No |

## Custom Capabilities

PerfLocale adds 7 custom capabilities that control access to specific plugin features. The canonical list is `TranslatorRole::CAPABILITIES`.

### Capability Matrix

| Capability | Translator | Editor | Administrator | Description |
| --- | --- | --- | --- | --- |
| perflocale_translate | Yes | Yes | Yes | Create and edit translations |
| perflocale_manage_translations | No | Yes | Yes | Supervisor cap: Strings screen, bulk machine translation, other users’ background jobs |
| perflocale_approve_translations | No | Yes | Yes | Reserved for approval integrations (no core screen consumes it yet) |
| perflocale_manage_languages | No | No | Yes | Add, edit, delete languages |
| perflocale_manage_addons | No | No | Yes | Manage plugin addons |
| perflocale_use_mt | Yes | Yes | Yes | Use machine translation (incl. bulk jobs) |
| perflocale_import_export | No | No | Yes | Import/export translations (XLIFF, PO/MO, JSON) |

One capability from earlier builds — `perflocale_manage_glossary` — was retired along with the glossary feature. It is still listed internally as a legacy capability so that deactivation and uninstall strip it from roles that were granted it by an older version.

### Capability Details

#### `perflocale_translate`

The core translation capability. Users with this cap can:

-   Create translation versions of posts, pages, and terms
-   Edit existing translations
-   See the Translations panel in the block/classic editor

#### `perflocale_manage_translations`

Supervisor-level access:

-   View and edit the **Strings** admin screen
-   Dispatch bulk machine translation from the Translations page and the `POST /translations/bulk-translate` endpoint
-   Cancel, retry, delete and read the full `args` of background jobs dispatched by _other_ users

#### `perflocale_approve_translations`

A reserved sign-off capability for approval-style integrations. No core screen or route consumes it today — it exists so an addon or custom code can gate an approve/reject step (a translation submitted as a suggestion, published only after sign-off) without granting reviewers the broader `perflocale_manage_translations` access. Granted to Editors and Administrators but **not** the base Translator role. Note that publishing a translation post itself is governed by WordPress’s native `publish_posts` capability, which the Translator role deliberately lacks.

Because it is a separate capability, you can grant it on its own to a dedicated reviewer role without also handing over the broader `perflocale_manage_translations` access.

#### `perflocale_manage_languages`

Administrative control over languages:

-   Add new languages
-   Edit language settings (name, locale, flag, direction)
-   Delete languages
-   Set the default language
-   Reorder languages

#### `perflocale_manage_addons`

Addon configuration on the **WP Admin → PerfLocale → Addons** page. Holders of this cap can:

-   View the Addons page (addon cards, status, version-mismatch notices)
-   Save an addon’s settings form on its subtab under **Settings → Addons** (reached from the card’s **Manage →** link) — persists to the autoloaded `perflocale_addon_settings` option keyed by addon ID
-   Click the per-card **Enable / Disable** button — writes to `perflocale_disabled_addons` and flushes the bootable-addons transient so the change takes effect on the very next request. Works for both bundled (in-plugin) and external addons

Addons themselves still auto-activate based on host plugin / theme detection — this cap controls who can _configure_ them, not whether they load.

#### `perflocale_use_mt`

Machine translation access:

-   Trigger machine translation for individual posts
-   Access the "MT" button in the editor sidebar
-   Start, monitor, and cancel bulk translation jobs (entire post types)
-   Uses the configured MT provider (DeepL, Google, Microsoft, LibreTranslate, an external agency, or the WordPress AI Client on WP 7.0+)

#### `perflocale_import_export`

Data portability:

-   Export translations to XLIFF 2.0 format
-   Import XLIFF files
-   Export string translations to PO/MO files
-   Run the full-site JSON export / import under **Settings → Export & Import**

Migrating from WPML, Polylang, or TranslatePress is gated separately on `manage_options`.

## Admin Menu Visibility

| Menu Item | Required Capability | Notes |
| --- | --- | --- |
| Dashboard | perflocale_translate |  |
| Languages | perflocale_manage_languages |  |
| Strings | perflocale_manage_translations |  |
| Translations | perflocale_translate | Filterable content list with per-language status |
| Addons | perflocale_manage_addons |  |
| Jobs | perflocale_translate | Background-jobs queue; per-row mutation rules below |
| Settings | manage_options (WP core) |  |

## REST API Permissions

| Endpoint | Required Check |
| --- | --- |
| GET /languages | Public by default (no auth required) - gateable via perflocale/api/languages_public filter |
| POST /languages | perflocale_manage_languages |
| PUT/DELETE /languages/{slug} | perflocale_manage_languages |
| POST /languages/reorder | perflocale_manage_languages |
| GET /translations/{type}/{id} | perflocale_translate + edit_post |
| POST /translations/{type}/{id} | perflocale_translate + edit_post |
| POST /translations/post/{id}/language | perflocale_translate + edit_post |
| PUT /translations/{type}/{id}/{lang} | perflocale_translate + edit_post |
| DELETE /translations/{type}/{id}/{lang} | perflocale_translate + delete_post |
| POST /translations/bulk-translate | perflocale_manage_translations |
| GET /strings | perflocale_translate |
| POST /strings/scan | manage_options |
| POST /strings/machine-translate | perflocale_use_mt |
| POST /machine-translate, POST /machine-translate/estimate, POST /machine-translate/object | perflocale_use_mt + per-object edit_post / read_post |
| POST /block-translate, POST /block-translate/from-source | perflocale_use_mt + edit_post |
| POST /xliff/export, POST /xliff/import | perflocale_import_export (export additionally checks edit_post per requested post) |
| POST /webhooks, GET /webhooks, DELETE /webhooks/{id} | manage_options |
| GET /config | Public, but only registered when the edge_integration_enabled setting is on |
| GET /jobs, GET /jobs/{id} | perflocale_translate (args redacted for non-creator non-supervisor users) |
| POST /jobs/{id}/cancel, POST /jobs/{id}/retry, DELETE /jobs/{id} | perflocale_translate + (creator OR perflocale_manage_translations) |

## WordPress Abilities API Permissions

When the Abilities API integration is enabled (via the [`perflocale/abilities/enabled`](https://perflocale.com/docs/hooks/#perflocale-abilities-enabled) filter on WP 6.9+), each registered ability ships its own `permission_callback`. AI tools and external consumers must satisfy these checks per call.

| Ability | Required Check |
| --- | --- |
| perflocale/list-languages | Public |
| perflocale/get-translations | perflocale_translate |
| perflocale/detect-language | perflocale_translate |
| perflocale/convert-url | Any logged-in user — override with the perflocale/abilities/convert_url_permission filter |
| perflocale/translate-post | perflocale_use_mt (per-target edit_post applied inside the callback) |
| perflocale/create-translation | perflocale_translate (per-target edit_post applied inside the callback) |

## Background Jobs Permissions

The background-processing system runs each operation under the originating user's capability set. Two distinct cap checks happen:

### At dispatch time

Each job type declares the capability it needs in its `get_required_capability()` method. `Dispatcher::dispatch()` checks `current_user_can()` against that cap; if the caller lacks it, the dispatch returns `['mode' => 'denied']` without queueing anything.

| Job type | Required capability |
| --- | --- |
| string_scan | perflocale_translate |
| data_import, data_export | perflocale_import_export |
| bulk_translate, site_translate | perflocale_manage_translations (worker also re-checks edit_post per source) |
| bulk_string_translate | perflocale_use_mt |
| wpml_migration, polylang_migration, translatepress_migration | manage_options (WP core) |

### At worker execution time

When the async worker fires (possibly minutes after dispatch), it **re-checks** the cap against the originating user ID stored on the job row — not against the user running the cron / Action Scheduler context (which is typically user 0). If the originating user has been deleted or had their role downgraded between dispatch and execution, the job is force-failed with _"Permission revoked or dispatching user no longer has access."_

This is defense-in-depth against a user's translator cap being removed while a job they dispatched is still in flight.

### When managing other users' jobs

Mutation endpoints (`cancel`, `retry`, `delete` via REST or CLI) check the dispatching user's identity against the current user:

-   If you dispatched the job yourself: you can cancel / retry / delete it.
-   If someone else dispatched it: you need `perflocale_manage_translations` (the supervisor cap).

The list / get endpoints also redact the `args` field for jobs you didn't dispatch unless you hold the supervisor cap. Use case: in a team where translators dispatch their own scans, a translator can see other translators' jobs are in flight but can't read their args (which may contain file paths) or interfere with them.

## Programmatic Usage

### Check capabilities in PHP

```php
use PerfLocale\Admin\TranslatorRole;

// PerfLocale caps are registered on WordPress roles, so check them with the
// standard core current_user_can() function.
if ( current_user_can( 'perflocale_translate' ) ) {
	// User can translate.
}

if ( current_user_can( 'perflocale_manage_languages' ) ) {
	// User can manage languages.
}

// The canonical list of custom capabilities is exposed as a public constant.
$all_caps = TranslatorRole::CAPABILITIES;
```

### Grant a capability to a custom role

```php
$role = get_role( 'my_custom_role' );

if ( $role ) {
	$role->add_cap( 'perflocale_translate' );
	$role->add_cap( 'perflocale_use_mt' );
}
```

### Remove all PerfLocale capabilities

On plugin deactivation, call:

```
PerfLocale\Admin\TranslatorRole::remove_roles();
```

This removes the Translator role and all custom capabilities from all roles.

## Hooks

### `perflocale/roles/editor_caps`

Filters the capabilities granted to the **Editor** role when the plugin activates. Return an empty array to prevent Editors from receiving any PerfLocale capabilities. Return a subset to restrict them to specific ones. The Administrator role is unaffected by this filter.

```
// Remove ALL PerfLocale capabilities from the Editor role.
add_filter( 'perflocale/roles/editor_caps', '__return_empty_array' );

// Grant only translation access to Editors (no bulk-translate, no approve rights).
add_filter( 'perflocale/roles/editor_caps', function ( array $caps ): array {
	return [
		'perflocale_translate' => true,
		'perflocale_use_mt'    => true,
	];
} );
```

**Note:** Capability grants are written to the database the first time the plugin activates (version-gated). If you add this filter to an existing install, reset the grant by running `delete_option('perflocale_caps_version')` in a one-time hook, then let WordPress reload - the filter will apply on the next `admin_init`.

### `perflocale/roles/cap_roles`

Filters which WordPress roles have PerfLocale capabilities **removed** on plugin deactivation or uninstall. This filter fires in three places - `TranslatorRole::remove_roles()` (deactivation), and both the full-wipe and preserve-data branches of `uninstall.php`.

```
// Do NOT strip caps from the Editor role on deactivation / uninstall.
// Useful if you manage editor access separately and don't want it wiped.
add_filter( 'perflocale/roles/cap_roles', function ( array $roles ): array {
	return array_diff( $roles, [ 'editor' ] ); // keeps ['administrator']
} );

// Extend cleanup to a custom role that was granted caps programmatically.
add_filter( 'perflocale/roles/cap_roles', function ( array $roles ): array {
	$roles[] = 'shop_manager';
	return $roles;
} );
```
