User Manager for Polylang
Limit Editors, Authors, Shop Managers and other non-administrator users to the Polylang languages, post types and taxonomies you choose. The rules are enforced as WordPress capabilities, so they also cover quick edit, bulk actions and the REST API.
Version 2.7.0 WooCommerce plugin Paid plugin Requires WordPress 6.5 Requires PHP 7.4 Tested up to 7.1 Updated 6 Oct 2026
1. Overview
User Manager for Polylang limits what non-administrator users can edit on a multilingual site that runs on Polylang. For each user you choose which Polylang languages, post types and taxonomies that user may work with. A French editor can then work on French content only, and a German shop manager can manage German products only. The plugin is listed on the Plugins screen as "Polylang User Manager" (the name in its plugin header). The licence screen uses the product name "User Manager for Polylang".
You set the rules in a Polylang access box on the Add New User and Edit User screens. The plugin enforces them as WordPress capabilities: it removes the permission to edit, delete or publish an item that is outside the user's rules. Because WordPress checks these capabilities for every route, the rules apply to the edit screens, the list tables, Quick Edit, bulk actions, admin AJAX, the block editor and the REST API (including the WooCommerce REST API). The plugin only removes permissions. It never gives a user a capability that the user's role does not already have.
How it works
- An administrator opens Users → Add New User or edits an existing user, and chooses Restricted in the Polylang access box.
- The administrator ticks the languages, post types and taxonomies the user may use, and saves the user. The choices are stored as user meta (see User meta).
- On every request the plugin reads the user's rules once, then answers WordPress's capability checks: content outside the rules gets
do_not_allow. - The plugin also keeps Polylang's admin language filter on the user's languages, forces new posts and terms into one of those languages, hides menus for post types and taxonomies the user may not use, and links a translation the user creates to its original.
At a glance
Several languages per user
Tick any number of languages (since 2.7.0). With none ticked, a restricted user may use every language but only the ticked post types and taxonomies.
Post types and taxonomies
A restricted user can only work with the public post types and taxonomies you tick. Media is never restricted by this plugin.
Real capabilities
Edit, delete and publish are denied through map_meta_cap, so Quick Edit, bulk actions, AJAX and the REST API respect the rules.
Language filter locked
The Polylang admin language filter only offers the user's languages. "Show all languages" is removed and a request for another language is replaced.
Linked translations
A restricted translator can create the translation of an existing item in an allowed language. The plugin links it to the original.
Users list and WP-CLI
A "Polylang access" column and two views on the Users screen, and the commands wp polylang-user-manager list, set and clear.
What it does not do
- It never restricts administrators (users with the Administrator role) or super admins on a multisite network.
- It does not stop a user from reading content. It removes the permission to edit, delete and publish. It adds no front-end output and does not filter front-end or REST read queries.
- It has no settings screen of its own. All choices are made per user, on the user's profile.
- It does not create roles or capabilities and does not change what a role can do. A user who may not edit pages by role still cannot edit pages when you tick Pages.
- It does not restrict non-public post types such as WooCommerce orders and coupons, or product variations on their own (a variation follows its parent product). Orders and coupons are not restricted by language.
- It does not stop a REST request that creates a new post of a post type the user has not been given. See Known limits.
- It does nothing without Polylang. See Requirements.
2. Requirements
| Item | Requirement | Notes |
|---|---|---|
| WordPress | 6.5 or later | From the Requires at least line. Tested up to 7.1 (from readme.txt). Core behaviour described on this page was checked against WordPress 7.1.2. |
| PHP | 7.4 or later | From the Requires PHP line. The 2.6.0 changelog notes PHP 8.x compatibility. |
| Polylang | Required: Polylang or Polylang Pro | The plugin starts only when the constant POLYLANG_VERSION exists or the class Polylang exists. The 2.6.0 changelog says it was tested with Polylang 3.8. The Polylang hooks this page names were checked against Polylang 3.8.10. |
| WooCommerce | Optional | Only needed if you restrict shop managers to products. Enable product translation in Polylang, or use Polylang for WooCommerce, so that products have a language. The WooCommerce-specific checks do nothing when WooCommerce is not installed. WooCommerce 11.1.2 was checked for the product variation behaviour. |
| Who can set the rules | A user with manage_options who may also edit the target user (edit_user) | The box is shown to users with manage_options; saving it also needs edit_user for the target user. By default WordPress gives manage_options to administrators. On multisite, WordPress 7.1.2 grants edit_user for another user only to users with manage_network_users (super admins), so a site administrator who is not a super admin cannot save rules for other users. |
| Languages | At least one Polylang language | If Polylang has no languages yet, the box shows "Polylang has no languages yet. Add them under Languages > Languages." |
When Polylang is not active, the plugin does not start. It shows this notice on every admin screen to users who can activate_plugins: "Polylang User Manager needs Polylang (or Polylang Pro) to be active." While it is not started, no restriction is enforced, and the licence screen and update checks of this plugin are not registered either (see Licence and updates).
3. Installation
- Install and activate Polylang (or Polylang Pro) and add your site languages under Languages → Languages.
- Open Plugins → Add New Plugin → Upload Plugin, choose the zip file from your purchase and click Install Now, then Activate.
- Open Plugins → WpExperts Hub Licences and activate your licence key to receive updates (see below). The plugin works without a licence.
- Open Users → Add New User, or edit a user, and use the Polylang access box (see The Polylang access box).
What activation creates
Nothing. The plugin has no activation routine: no database tables, no options, no roles, no scheduled events and no pages. Data is written only when you save a user's Polylang access, when a restricted user dismisses the information notice, when the plugin corrects Polylang's own pll_filter_content setting for a restricted user, and when the licence client stores a licence or its update-check cache (see Privacy). Versions before 2.7.0 created a database table that was never read. Version 2.7.0 no longer creates it, and uninstalling removes it.
Licence and updates
The plugin bundles the WpExperts Hub licence and update client (licence/class-wpxh-licence-client.php, client version 2.0.0). One screen manages every WpExperts Hub plugin on the site: Plugins → WpExperts Hub Licences. It needs the manage_options capability.
- Find your licence key in the purchase email, or under My Account → Downloads on wpexpertshub.com.
- Open Plugins → WpExperts Hub Licences. Each WpExperts Hub plugin has its own card with a status ("Active" or "Not active"), the installed version and, when known, "version X is available".
- Paste the key into Licence key and click Activate licence. Spaces and any character other than letters, digits and hyphens are removed from the key before it is sent.
- To move the plugin to another live site, click Deactivate licence first. The screen reminds you: "Deactivate before moving the plugin to another live site."
- If you cannot find your key, open "Can't find your key? Email it to me", enter the email address used for the purchase and click Send licence key. The licence server sends the email.
- Updates come through WordPress. An active licence puts the plugin on the normal Dashboard → Updates and Plugins screens. When an update exists but the server returns no package for your site, WordPress shows "Automatic update is unavailable for this plugin. Activate your licence to enable updates." under the plugin.
- The update information is cached. The answer from the licence server is kept in the site transient
wpxh_licence_check_v2for 12 hours. After a failed request the previous answer is kept and the check is retried after one hour. The cache is cleared when you activate or deactivate a licence, send a key request, or when any plugin is upgraded. - A reminder on the Plugins screen. On Plugins, users with
manage_optionssee "Activate your WpExperts Hub licence to receive updates for: ..." with a Manage licences link while any registered WpExperts Hub plugin has no active licence. - A link in the plugin row. This plugin adds "Activate License" or "Deactivate License" (the plugin's own wording) under its name on the Plugins screen. It links to the licence screen.
- Licence key only unlocks updates. No code in the plugin checks the licence status before enforcing restrictions. The status is only used for the link text and the licence screen. The plugin works the same with or without a licence. Whether the server returns an update package for a site is decided on the licence server.
- Without Polylang the licence client is not registered, because the plugin does not start. Activate Polylang to see the licence card and receive updates.
What the licence client sends to wpexpertshub.com, and when, is listed under Data sent to wpexpertshub.com.
Updating
Update from Dashboard → Updates (needs an active licence), or upload the new zip over the old plugin. There is no migration routine. Rules saved by versions before 2.7.0 keep working: a user who had a language assigned and has no saved mode stays restricted to those languages, and a user with no language stays unrestricted. Open and save the user's profile to move it to the new mode setting (see The Polylang access box).
Multisite
The plugin has no network admin screen. Super admins are never restricted. The rules are stored in user meta without a site prefix, so they belong to the user rather than to one site, while the language choices are Polylang term IDs of the site where they were saved. Uninstalling removes the user meta for all users and drops the old table on every site of the network. On a multisite network WordPress runs the same user_new_form hook on both the "Add Existing User" and the "Add New User" forms, so the Polylang access box is shown there. The plugin saves the box only through the hooks edit_user_created_user and edit_user_profile_update. In WordPress 7.1.2 the multisite Add New User form creates a signup (or activates it) without calling edit_user(), so edit_user_created_user does not fire and nothing you tick on either multisite form is saved. Create or add the user first, then set the rules from Users → Edit User. On multisite WordPress lets only super admins edit other users' profiles (edit_user maps to manage_network_users), so only a super admin can save the rules.
Deactivating and deleting
Deactivating the plugin keeps all saved rules and ends enforcement. Deleting it from the Plugins screen removes the rules (see What deactivation and uninstall remove). Deleting does not remove the licence options, so deactivate your licence on Plugins → WpExperts Hub Licences before you delete the plugin if you want the activation released.
4. Quick start
- Activate Polylang and this plugin. Make sure the user has the role you want (for example Editor or Shop Manager). Administrators are never restricted.
- Open Users → All Users, then Edit the user. Scroll to the Polylang access box. (For a new user use Users → Add New User; on multisite create the user first and then edit them, see Multisite.)
- Choose Restricted.
- Tick the Languages the user may work in. Tick none to allow every language.
- Tick the Post types (for example Posts, Pages, Products) and Taxonomies (for example Categories, Product categories) the user may use. Read the live summary under the box: it says what you are about to save.
- Click Update User (or Add User).
- Test it: open Users → All Users and check the Polylang access column. Then log in as the user (or use a private window) and open the Posts list: it lists only content in the user's language, because Polylang's language filter is locked to those languages. Open the edit address of an item in another language directly (
post.php?post=ID&action=edit): it shows "You do not have permission to access this page."
5. Features
Parts of this section describe what WordPress core does once this plugin has answered a capability check. Those parts were checked against WordPress 7.1.2 and can differ in other versions.
Who is restricted
A user is restricted only when all of these are true:
- The user is logged in and does not have the Administrator role, and is not a super admin.
- The saved mode (
_poly_user_restricted) is1, or no mode has been saved and the user has at least one language saved (profiles saved before 2.7.0). - Polylang is active, so that the plugin has started.
Everyone else is unrestricted: visitors, administrators, super admins, users set to Full access, and users who never had rules. A user whose saved mode is 0 keeps the saved choices, which are ignored until you switch the user back to Restricted.
The result can be changed in code with the polylang_user_manager_access filter (see Filters). The rules are worked out once per request for each user and kept in memory only. They are cleared when a language is added or changed in Polylang (actions pll_add_language and pll_update_language) and when a profile is saved. The plugin also listens for pll_delete_language, but Polylang 3.8.10 does not fire that action.
The Polylang access box
The box has the title Polylang access. It appears at the end of Users → Add New User and Users → Edit User, only for users who have manage_options. It does not appear on your own Users → Profile screen (an administrator is never restricted). It contains:
- Access. Two radio buttons. Full access: "Can work with every language, post type and taxonomy the role allows." Restricted: "Only the languages, post types and taxonomies ticked below." New users start on Full access.
- Languages. One tick box per Polylang language, with its flag, name and slug (for example "French fr"). Links Select all and Clear tick or untick the group. The note says: "Leave all unticked to allow every language (the post types and taxonomies below still apply)."
- Post types. One tick box per public post type except Media. Types that Polylang does not translate carry the small label "not translated". The note says: "The user can only add and edit the ticked post types. Items of a translated post type are also limited to the languages above."
- Taxonomies. One tick box per public taxonomy, again with "not translated" where Polylang does not translate it. The note says: "The user can only add and edit terms of the ticked taxonomies."
- Live summary. A line under the box that updates as you tick: "Full access to every language, post type and taxonomy." or for example "Restricted to French, German · 2 post types · 1 taxonomy". With no language ticked it reads "Restricted to every language". When nothing is ticked under Post types and Taxonomies it adds "Nothing is allowed yet: tick the post types and taxonomies this user may use."
The three groups are hidden while Full access is chosen. They are still submitted, so what you ticked is saved and kept if you switch back. If you change the Role field on the form to Administrator, the box hides the choices and shows "Administrators are never restricted." When you open an existing administrator, the box shows "Administrators are never restricted. Change the role to Editor, Author or Shop Manager to limit this user." instead of the choices. Because no choices are shown for an administrator, changing that user's role on the same form saves no rules: save the role first, then open the user again and set the box.
What is enforced, and where
The plugin answers WordPress's capability checks edit_post, delete_post, publish_post, edit_page, delete_page, edit_term and delete_term for a restricted user. It adds do_not_allow when the item is outside the user's rules. It does this in the map_meta_cap filter at priority 20, so it runs after most other plugins and can only take permission away. For post types registered without meta capability mapping (the plugin's code comment names WooCommerce product variations as an example), it also watches the type's own edit, delete and read capability, for example edit_product.
A post is outside the rules when any of these is true:
- Its post type is a public type (other than Media) that is not ticked, and the type is not opened with the
poly_user_open_accessfilter. - Its post type is translated by Polylang and the post's language is not one of the user's languages. A post that has no language is outside the rules, except an auto-draft (a new, unsaved item).
- It is a product variation whose parent product is outside the rules. A variation follows its parent.
A term is outside the rules when its taxonomy is a public taxonomy that is not ticked, or when the taxonomy is translated by Polylang and the term has a language that is not one of the user's languages. A translated term with no language is allowed. Polylang's own internal taxonomies and non-public taxonomies are never blocked.
| What the user tries | What happens |
|---|---|
| Open the Edit screen of an item outside the rules | WordPress stops with "You do not have permission to access this page." (HTTP 403, title "Error"). |
Open Add New for a post type that is not ticked, or with a new_lang value for a language the user may not use | The same message. |
Open the list screen of a post type that is not ticked (edit.php?post_type=...) | The same message. |
| See the post list | The Edit, Quick Edit, Trash and Delete Permanently row actions are removed from items outside the rules. Items the user may edit keep all their actions, so Quick Edit and bulk actions remain available. |
| Bulk edit items | Each item is checked with edit_post. WordPress 7.1.2 skips items the user may not edit. |
| Bulk Trash, restore or delete on the Posts list | Each item is checked with delete_post. In WordPress 7.1.2 one refused item stops the whole request with "Sorry, you are not allowed to move this item to the Trash." (or "restore this item from the Trash", "delete this item"). The language filter normally keeps other-language items out of the list. |
| Change a post's language to one the user may not use (Languages box, Quick Edit or bulk edit) | The request is stopped before Polylang saves anything: "You do not have permission to edit item." (HTTP 403). A second guard on pre_post_update answers "You do not have permission to add new item." when the post's original status is auto-draft. |
| Edit, delete or publish through the REST API, including the block editor and WooCommerce REST | The capability check fails and WordPress answers with its usual permission error (HTTP 403). |
| Create a term in a taxonomy that is not ticked, or in a language the user may not use | The term is refused with the error poly_error: "You do not have permission to add new term." |
| Open the Edit Term screen of a term outside the rules, or open a taxonomy that is not ticked | "You do not have permission to edit term." (HTTP 403). |
| Quick Edit a term, or move a term to a language the user may not use | The same message, before WordPress updates the term. |
| Edit a product variation, or a product, through WooCommerce's classic product screen or the WooCommerce REST API when the parent product is outside the rules | See WooCommerce products and variations. |
The Polylang admin language filter
Polylang remembers which language a user is filtering by in the admin, in the user meta key pll_filter_content. For restricted users the plugin keeps that filter on one of the user's own languages:
- Before Polylang reads the filter (action
setup_theme, priority 0, admin requests only, not admin AJAX), the stored filter is replaced by the user's first allowed language if it is not one of the user's languages. The very first request is already filtered. - A
langvalue in the address is accepted only if it names one of the user's languages (by slug or by language id). Anything else, includinglang=allandlang=0, is replaced by the current allowed language. - The language list in the admin bar (Polylang's "Filters content by language" menu) only lists the user's languages. The "Show all languages" item is removed, also for a restricted user who has no language ticked (every language allowed): such a user sees one language at a time.
- The plugin also filters Polylang's current and preferred admin language (
pll_admin_current_languageandpll_admin_preferred_language, priority 20) so that a disallowed language is replaced by the first allowed one. - The language columns of the post and term lists show only the user's languages.
Because the stored value is a Polylang setting, it stays on the language it was last set to when you later give the user Full access. The user can choose "Show all languages" again from the admin bar.
Language of new posts and terms
- Add New screens. For an allowed, Polylang-translated post type (and for the terms screen of an allowed, translated taxonomy), a request without a
new_langvalue is redirected to the same screen withnew_langset to the user's preferred language. The preferred language is Polylang's preferred language (pref_lang: the admin language filter, or the site's default language when no filter is set) if the user may use it, otherwise the first allowed language. - Saving. After WordPress saves a post (hook
wp_insert_post, priority 999, in the admin and in REST requests), a new post whose language is not allowed is set to the preferred language. For an update, a post whose language changed to a disallowed one is set back to its previous language (or to the preferred language if it had none). Types that Polylang does not translate, revisions, and post types opened withpoly_user_open_accessare skipped. - Terms. After a term is created (hook
created_term, priority 999, in the admin and in REST requests) in a translated taxonomy, a language that is not allowed is replaced by the preferred language. This also covers terms created in a block editor or through the WooCommerce REST API. - Language drop-downs. In the Languages box, Quick Edit, bulk edit and term forms, the Polylang language drop-downs (
post_lang_choice,term_lang_choice,inline_lang_choice) only list the user's languages. A small script does this in the browser, including for Quick Edit rows that WordPress clones when you open them. The server refuses a request for another language anyway. - Translations table. The inputs of Polylang's translations table (
#post-translations,#term-translations) are read-only for restricted users. On post screens the table is also hidden (by a style rule in the classic editor and by the block editor script), and the classic editor's Duplicate button is hidden.
Creating a translation
A restricted translator can create the translation of an existing item in any of their own languages. The user clicks the "+" for their language in the Polylang language column of the source item (Polylang's own link, which opens Add New with from_post and new_lang), writes the translation and saves. The plugin then links the new item to the source through Polylang's own function. In the block editor the link is made by a short request that the editor sends after each save (see AJAX actions).
The link is made only when all of these are true:
- The language is one of the user's languages and it is the language the new item really has.
- The source item exists, has the same post type as the new item, and the user may read it.
- The source item does not already have a translation in that language in another post. The plugin never replaces an existing translation.
If the source item has no translation group yet, the group is created from the source item's language. If you save an ordinary item (no source), the plugin makes sure it is in its own translation group.
Menus, "+ New" and screens
- The top-level menu entries of public post types that are not ticked (except Media) are removed (action
admin_menu, priority 9999). The submenu entries of public taxonomies that are not ticked are removed as well. - The "+ New" items of those post types are removed from the admin bar.
- Hiding a menu is only cosmetic. The screens themselves refuse access, as listed in What is enforced, and where.
- On the profile of the user, and in the notice below, the names are the labels WordPress shows (for example "Posts", "Product categories").
The information notice
A restricted user sees a dismissible blue notice on the Dashboard, the post lists and the term lists: "Polylang access Your account is limited to content in: French, German. Post types: Posts, Pages. Taxonomies: Categories." The sentence for a restricted user with every language reads "Your account is limited to the content types below, in every language." The "Post types" and "Taxonomies" parts are left out when none is ticked. When the user dismisses the notice, the plugin stores _pllum_notice_dismissed for that user and never shows it again.
Restricted users who do not have manage_options also see a read-only Polylang access table on their own profile with the rows Languages ("Every language" when none is ticked), Post types and Taxonomies ("None" when empty), and the text "An administrator limited the content you can edit. You can change this only by asking them."
The Users list
On Users → All Users the plugin adds:
- A Polylang access column. It shows "Never restricted" for administrators and super admins, "Full access" for unrestricted users, and for restricted users one badge per language (or "All languages") followed by the number of post types and taxonomies, for example "2 post types · 1 taxonomy". Clicking a language badge lists the restricted users of that language (address
users.php?pll_access=restricted&pll_lang=fr); restricted users who have every language are included. A "Language removed" badge appears when a language assigned to the user no longer exists. - Two views above the table: Restricted by Polylang with a count, and Not restricted by Polylang. The second one lists every user who is not in the first list, administrators included.
The view count and the filter look at up to 5,000 users that have any saved rule.
WooCommerce products and variations
WooCommerce is optional. If you use it, tick Products and the product taxonomies (for example Product categories, Product tags) for the shop manager, and make products translatable in Polylang (or use Polylang for WooCommerce). If products are not translated, only the Products tick applies, not the language.
- Variations follow the parent product. A variation of a product outside the user's rules cannot be edited or deleted. This holds in the REST API and in the classic product screen's AJAX requests. This protection arrived in 2.7.0.
- REST API. A write request (any method except GET) to
/wc/v<n>/products/<id>/variationsor a path below it is refused withrest_forbidden("Sorry, you are not allowed to do that.", HTTP 403) when the parent product is outside the rules. The check runs onrest_pre_dispatchand covers batch requests below that path. - Product categories and tags in the REST API. WooCommerce's term permission check (
woocommerce_rest_check_permissions, priority 20) is also answered with the term rules, for the edit and delete contexts. - Classic product screen AJAX. For admin AJAX actions whose name starts with
woocommerce_, the request fieldsproduct_id,post_id,variation_id,variation_idsandvariable_post_idare checked. If one names an item outside the rules, the request ends with-1and HTTP 403. - Orders and coupons. They are not public post types, so they are not restricted by this plugin.
A language that was deleted
If a language assigned to a user is later deleted in Polylang, the user is not silently unrestricted. The Edit User screen shows "A language assigned to this user no longer exists. Until you choose a language below, this user cannot edit translated content." The Users list shows a "Language removed" badge. If the deleted language was the only one assigned, the user has no allowed language and cannot edit any content of a translated post type until you choose a language again. If other assigned languages still exist, the user keeps working in those.
Leaving a post type open
The filter poly_user_open_access returns the names of post types that restricted users may use without ticking them. For those types the plugin skips its post type check, the language checks on save, the language-change guards on the edit screens and the row-action removal. Read Known limits first: for a post type that Polylang translates, WordPress's edit, delete and publish capability checks still compare the item's language with the user's languages. The filter is aimed at post types that Polylang does not translate. See Filters for the code.
Known limits
- Creating through the REST API. The capability checks run on existing items. When a restricted user creates a new post with a REST request, WordPress 7.1.2 asks only for the post type's own create capability (and its publish capability when the new post is published). The plugin does not add a check for the post type tick on creation. It does set the language of the new post to an allowed one. New terms are checked (taxonomy and language). On the admin screens, a post type that is not ticked is refused on Add New and its menu is hidden.
- Reading is not restricted. The plugin does not hide other-language content from REST read requests or from the front end.
- Open post types that Polylang translates. As explained under Leaving a post type open, listing such a type in
poly_user_open_accessdoes not lift the language check inside WordPress's edit, delete and publish capabilities. - Assigning terms. The plugin does not check the capability to assign a term to a post. It limits creating, editing and deleting terms.
- Media. Media is never blocked by the post type rule. If Polylang translates media, the language rule applies to media items.
- Large sites. The "Restricted by Polylang" view and the Users list filter read at most 5,000 users that have saved rules.
WP-CLI
Three commands manage the rules from the command line. They exist only while Polylang is active (without it the plugin does not start). Full details are in WP-CLI commands.
6. Settings
There is no settings screen and no site-wide option. All settings are per user, in the Polylang access box described in The Polylang access box. The form field names below are what the box submits; the user meta key is where the value is stored.
| Setting (label) | Form field and user meta key | Default | What it does |
|---|---|---|---|
| Access: Full access / Restricted | poly_user_restricted stored as _poly_user_restricted | Full access (0) for a new user | 1 restricts the user to the choices below, 0 leaves the user unrestricted and keeps the choices. A profile that has no saved mode counts as restricted when it has a language, otherwise as unrestricted. A form submitted without the field (a form from before 2.7.0) saves 1 when a language is ticked, otherwise 0. |
| Languages | poly_user_lang[] stored as _poly_user_lang | None ticked | The Polylang languages the user may use, stored as a list of Polylang language term IDs. None ticked means every language (including languages added later). Only IDs of existing languages are saved; duplicates are dropped. Any number of languages. |
| Post types | poly_post_types[] stored as _poly_user_post_types | None ticked | The public post types the user may add and edit, stored as a list of post type names. Media (attachment) is never offered. Only names of listed post types are saved. None ticked means no public post type is allowed. |
| Taxonomies | poly_tax_types[] stored as _poly_user_tax_types | None ticked | The public taxonomies whose terms the user may add and edit, stored as a list of taxonomy names. Only names of public taxonomies are saved. None ticked means no public taxonomy is allowed. |
| (information notice dismissed) | stored as _pllum_notice_dismissed | Not set | Set to 1 when the user dismisses the blue notice. It is not shown in any form. Delete the meta key to show the notice again. |
The form is saved only when the request carries the plugin's own nonce (pllum_nonce, action pllum_save_access), the current user has manage_options and may edit the target user. Other plugins that fire the same profile hooks cannot clear a user's rules (a fix in 2.7.0). Saving a profile of an administrator writes empty lists and mode 0, because the box shows no choices for administrators.
7. Developer reference
Filters
| Filter | Parameters and return | When it runs |
|---|---|---|
poly_user_open_access | Array of post type names (starts empty). Return the array with the post types that restricted users may use without a tick. | Once per restricted user and request, while the rules are worked out. It does not run for unrestricted users. The name is the original ("legacy") hook name and is kept for compatibility. |
polylang_user_manager_access | $access (array, see below), $user_id (int). Return the array. | For every user the plugin looks at, including administrators and unrestricted users, once per user and request (the result is cached for the request). |
The $access array has these keys:
| Key | Type | Meaning |
|---|---|---|
restricted | bool | Whether the user is restricted. |
langs | array | Allowed language slugs, as slug => slug. For a restricted user with no language chosen it holds every Polylang language. |
ids | int[] | Polylang language term IDs (the language-specific term of the term_language taxonomy, or the language term if that is missing). |
lang_ids | array | Language term ID => slug, from the language taxonomy. |
posts | string[] | Ticked post type names. |
taxs | string[] | Ticked taxonomy names. |
open_types | string[] | Result of poly_user_open_access. |
all_languages | bool | True when the user is restricted but has no language chosen. |
stale | int | How many assigned languages no longer exist. |
Example: leave the post type contact open for restricted users (best for post types that Polylang does not translate, see Known limits):
add_filter( 'poly_user_open_access', function ( $post_types ) {
$post_types[] = 'contact';
return $post_types;
} );
Example: give every restricted user the event post type as well, without ticking it on each profile:
add_filter( 'polylang_user_manager_access', function ( $access, $user_id ) {
if ( $access['restricted'] && ! in_array( 'event', $access['posts'], true ) ) {
$access['posts'][] = 'event';
}
return $access;
}, 10, 2 );
The Users list column, the notice, the own-profile summary and the WP-CLI list command read the same rules, so this filter changes what they show for the users it touches. The Polylang access box on Add New User and Edit User shows the saved user meta (the ticks and the mode), not the filtered result; only its "language no longer exists" warning uses the filtered rules.
AJAX actions
| Action | Who | What it does |
|---|---|---|
pllum_dismiss_notice | Logged-in users (wp_ajax_ only) | POST field nonce (action pllum_dismiss_notice). Stores _pllum_notice_dismissed = 1 for the current user and answers an empty HTTP 200. |
gutenberg_post_save_action | Logged-in users | POST fields nonce (action pll_user_gutenberg_save), post_id, lang, from_post. For a restricted user with an allowed language who may edit the post, links the new translation (see Creating a translation). Always ends with an empty answer. Sent by js/pll-user.js after each completed save in the block editor. |
pll_terms_not_translated | Polylang's own action | The plugin attaches at priority 1. If the request field translation_language is not one of a restricted user's languages, the request ends with HTTP 403. |
inline-save | WordPress Quick Edit | The plugin attaches at priority -1 to refuse a language change the user may not make. |
WP-CLI commands
The commands are registered under wp polylang-user-manager when WP-CLI runs and Polylang is active. A <user> argument is a numeric ID, an email address or a login.
| Command | Arguments and flags | What it does |
|---|---|---|
wp polylang-user-manager list | [--format=<format>]: table (default), csv, json or yaml | Lists users that have saved Polylang rules (either the mode or the languages meta key exists). Columns: ID, login, role, restricted (yes or no), languages (slugs, or all), post_types, taxonomies. The last three are empty for users that are not restricted. |
wp polylang-user-manager set <user> | [--languages=<slugs>] comma-separated Polylang slugs (leave out for every language); [--post-types=<names>]; [--taxonomies=<names>] | Restricts the user: writes the three lists and sets the mode to restricted. Errors: "Administrators are never restricted.", "Unknown language 'xx'. Available: ...", "Unknown post type 'x'.", "Unknown taxonomy 'x'.", "User 'x' not found.". It checks that post types and taxonomies exist; it does not check that they are public. Success message: "<login> is restricted." |
wp polylang-user-manager clear <user> | [--forget] | Gives the user full access (mode 0) and keeps the saved lists. With --forget it deletes the four rule meta keys instead. Success message: "<login> has full access." |
wp polylang-user-manager set editor_fr --languages=fr --post-types=post,page --taxonomies=category
wp polylang-user-manager set 42 --languages=fr,de --post-types=product --taxonomies=product_cat,product_tag
wp polylang-user-manager list --format=csv
wp polylang-user-manager clear editor_fr
wp polylang-user-manager clear editor_fr --forget
If the main plugin object is missing, the commands stop with "Polylang is not active."
User meta
| Key | Value | Written by |
|---|---|---|
_poly_user_lang | Array of Polylang language term IDs | Profile save, wp polylang-user-manager set. Removed by clear --forget and by uninstall. |
_poly_user_post_types | Array of post type names | Profile save, set. |
_poly_user_tax_types | Array of taxonomy names | Profile save, set. |
_poly_user_restricted | 1 restricted, 0 full access, absent: restricted when languages are saved (before 2.7.0) | Profile save, set (1), clear (0). |
_pllum_notice_dismissed | 1 | The dismiss AJAX action. |
pll_filter_content | Language slug (Polylang's key, not this plugin's) | Polylang. This plugin also updates it for restricted users, see The Polylang admin language filter. |
Options, transients and tables
The plugin has no options and no tables of its own. The licence client uses these:
| Name | Kind | Content |
|---|---|---|
_polylang-user-manager_licence_key | Option (not autoloaded) | The licence key, after a successful activation. |
_polylang-user-manager_key_status | Option (not autoloaded) | active after activation. Set to inactive when the licence server says the licence is no longer active for the site. Removed on deactivation. |
wpxh_licence_check_v2 | Site transient, 12 hours | The cached answer of the licence server, shared by all WpExperts Hub plugins. |
wpxh_licence_msg_<user id> | Transient, 2 minutes | The message shown once after you use the licence screen. |
Versions before 2.7.0 created the table {prefix}poly_user_from. Nothing reads it. Uninstall drops it.
Core and other-plugin hooks the plugin attaches to
These are listed so that you can tell which other code runs before or after it. Priorities are exactly as in the code.
| Hook | Priority | Purpose |
|---|---|---|
map_meta_cap | 20 | Adds do_not_allow for items outside the rules. It never grants a capability. |
woocommerce_rest_check_permissions | 20 | Applies the term rules to WooCommerce REST category and tag edits and deletes. It replaces a "true" from WooCommerce or earlier filters with the plugin's answer for those terms. |
rest_pre_dispatch | 10 | Refuses writes below /wc/v<n>/products/<id>/variations for a parent product outside the rules. |
set_current_user | 1 | Resets the cached rules when the current user changes. |
plugins_loaded | 20 | Starts the plugin when Polylang is present. |
init | 10 | Registers the licence client; registers the WP-CLI command when WP-CLI runs. |
admin_init | 1, 999 | Priority 1: guards WooCommerce AJAX. Priority 999: refuses admin requests that carry the parameters vc_action, post_type and post_id when the post is outside the rules, and redirects Add New screens to the user's language. |
pre_post_update | 1, 999 | Remembers the post's language; refuses a language change the user may not make. |
wp_insert_post | 999 | Keeps new and updated posts in an allowed language. |
created_term | 999 | Keeps new terms in an allowed language. |
pre_insert_term | 999 | Refuses new terms in a taxonomy or language that is not allowed. |
edit_terms | 1 | Refuses term changes outside the rules. |
load-post.php, load-post-new.php, load-edit.php, load-edit-tags.php, load-term.php | 1 or 10 | Screen guards (access refused, language change refused). |
wp_ajax_inline-save | -1 | Refuses a Quick Edit language change the user may not make. |
post_row_actions, page_row_actions | 999 | Removes Edit, Quick Edit, Trash and Delete Permanently (and the edit_vc link) for items outside the rules. |
manage_<screen id>_columns and <taxonomy>_row_actions | 999 | Removes other languages' language columns, and Edit, Quick Edit and Delete links of terms outside the rules. |
admin_menu, admin_bar_menu | 9999, 999 | Hides menu entries and "+ New" items. |
admin_notices | 10 | The information notice, and the "needs Polylang" notice. |
setup_theme | 0 | Corrects Polylang's admin language filter before Polylang reads it. |
pll_admin_current_language, pll_admin_preferred_language, pll_admin_languages_filter | 20 | Keep Polylang's admin language, preferred language and admin bar list on allowed languages. |
pll_before_post_translations | 1 | Outputs the pll_from_post field and hides the classic translations box. |
block_editor_meta_box_hidden_fields | 1 | Outputs the hidden fields pll_post_lang_choice and pll_from_post for the block editor. |
save_post | 10 | Links a new translation to its source after a classic save. |
admin_enqueue_scripts, enqueue_block_editor_assets | 20, 10 | Load js/common.js and js/pll-user.js for restricted users with allowed languages. |
admin_enqueue_scripts (profile assets) | 10 | Loads css/admin.css on user-edit.php, user-new.php, profile.php and users.php, and js/profile.js on user-edit.php and user-new.php. |
current_screen | 10 | Registers the list-table filters for the current screen. |
user_new_form, edit_user_profile | 999 | Draw the Polylang access box. |
show_user_profile | 999 | Draws the read-only summary for restricted users on their own profile. |
edit_user_created_user, edit_user_profile_update | 999, 9999 | Save the box. |
manage_users_columns, manage_users_custom_column, views_users, pre_get_users | 10 | The Users list column, views and filter. |
plugin_action_links_polylang-user-manager/polylang-user-manager.php | 10 | The "Activate License" or "Deactivate License" link. |
Licence client: admin_menu, admin_init, admin_notices, pre_set_site_transient_update_plugins, plugins_api, http_request_args, http_request_host_is_external, upgrader_process_complete, in_plugin_update_message-<plugin basename> | 10, except plugins_api (20) | Registered once for all WpExperts Hub plugins: the licence screen and its form handling, the Plugins screen reminder, the update data, the "View details" window, and the local-server exceptions for certificate checks. See Licence and updates. |
Capabilities and roles
- Setting rules:
manage_optionsandedit_userfor the target user. - The "needs Polylang" notice:
activate_plugins. - The licence screen and its actions:
manage_options. - The capabilities the plugin denies:
edit_post,delete_post,publish_post,edit_page,delete_page,edit_term,delete_term, and the matching type-specific primitives for post types registered without meta capability mapping. - The plugin adds no roles and no capabilities.
Form fields, scripts and constants
- Profile form fields:
poly_user_restricted,poly_user_lang[],poly_post_types[],poly_tax_types[], noncepllum_nonce. - Fields the plugin reads from Polylang forms:
post_lang_choice,inline_lang_choiceandterm_lang_choice(a value of-1is ignored),translation_language, and the plugin's own hidden fieldspll_post_lang_choiceandpll_from_post. - Scripts:
pllum-profile(js/profile.js, Add New User and Edit User only),pll-user-admin(js/common.js, localised objectpll_user) andpll_user_block(js/pll-user.js, block editor). The profile stylesheet ispllum-admin(css/admin.css). - Constants: the plugin defines
POLYLANG_USER_MANAGER_VERSION. The licence client readsWPXH_LICENCE_SERVERif you define it. It also has the filterswpxh_licence_serverandwpxh_licence_sslverify. Certificate checks are switched off only when the licence server host ends in.local,.testor.localhost, or islocalhostor127.0.0.1. - Main object:
$GLOBALS['polylang_user_manager']holds the instance ofPolylang_User_Manager. Its public methods (get_user_access(),user_can_access_post(),user_can_access_term(),post_type_allowed(),taxonomy_allowed(),get_language_map()) are not a documented interface and can change.
File layout
| File | Purpose |
|---|---|
polylang-user-manager.php | Main class: rules, capability filters, REST and AJAX guards, language filter, menus, notice. |
classes/class-polylang-user-manager-profile.php | The Polylang access box, saving, own-profile summary, Users list column and views. |
classes/class-polylang-user-manager-cli.php | WP-CLI commands. |
js/profile.js, js/common.js, js/pll-user.js, css/admin.css | Profile box behaviour, language drop-down filtering, block editor translation link, profile styling. |
licence/class-wpxh-licence-client.php | The bundled licence and update client. |
uninstall.php, languages/ | Clean-up on delete; translation template. |
8. Privacy
What is stored
- User meta for each user you save rules for: the five keys listed under User meta. They contain language IDs, post type names, taxonomy names, a mode flag and a notice flag. They contain no personal data beyond the link to the user account.
- Polylang's
pll_filter_contentuser meta is updated for restricted users, as explained under The Polylang admin language filter. - Licence data, if you activate a licence: the licence key and its status as options, and the cached licence-server answer as a site transient (see Options, transients and tables).
- The plugin sets no cookies, writes no log, creates no tables and keeps no copy of content.
- It does not register privacy policy text or personal data exporters or erasers.
Data sent to wpexpertshub.com
Only the bundled licence client contacts another server. It sends JSON by HTTP POST to https://wpexpertshub.com/wp-json/wphub-licence/v1/<route> (the server can be changed with the WPXH_LICENCE_SERVER constant or the wpxh_licence_server filter), with a 20 second timeout. The routes and what each one sends:
| Route | When | Data sent |
|---|---|---|
activate | You click Activate licence. | The plugin slug (polylang-user-manager), the licence key, and your site address (site_url()). |
deactivate | You click Deactivate licence. | The slug, the saved key and your site address. The key is removed from the site even when the server cannot be reached. |
send-key | You use "Can't find your key? Email it to me". | The slug and the email address you typed. |
check | When WordPress refreshes its plugin update list (pre_set_site_transient_update_plugins, which includes WordPress's scheduled checks and Dashboard → Updates → Check again), and when the plugin's "View details" window is opened, unless a cached answer for the same data is younger than 12 hours (one hour after a failed request). | Your site address and, for every WpExperts Hub plugin registered on the site (not only this one): the plugin slug, the licence key (empty if none) and the installed version. |
WordPress adds its usual User-Agent header to every remote request: "WordPress/<version>; <site address>" (checked in WordPress 7.1.2). The plugin sends no content, no user data and no data about your Polylang setup. What the licence server stores or logs cannot be seen from the plugin's code.
What deactivation and uninstall remove
- Deactivation removes nothing.
- Uninstall (deleting the plugin on the Plugins screen) deletes the user meta keys
_poly_user_lang,_poly_user_post_types,_poly_user_tax_types,_poly_user_restrictedand_pllum_notice_dismissedfor all users, and drops the table{prefix}poly_user_fromof earlier versions on every site (on a multisite network, every site). It does not touch Polylang'spll_filter_content. - Uninstall does not delete the licence options, the licence transients or the activation on the licence server. Deactivate the licence first.
9. Troubleshooting
| Problem | Likely cause and fix |
|---|---|
| A user can still edit everything. | Check the Polylang access column on the Users list. Common reasons: the user is an administrator or super admin ("Never restricted"); the mode is Full access; the profile was never saved with the box (a user with no saved mode and no language is unrestricted); Polylang is not active (the plugin has not started). If the item is in a post type that Polylang does not translate, only the post type tick applies, not the language. |
| The notice "Polylang User Manager needs Polylang (or Polylang Pro) to be active." appears. | Activate Polylang. Until then no restriction, licence screen or update check from this plugin is active. |
| The Polylang access box is missing on the user screen. | You need manage_options. The box is not shown on your own profile. For an administrator it shows only the note "Administrators are never restricted." On multisite's Add Existing User and Add New User forms it is shown but not saved (see Multisite). |
| The box says "Polylang has no languages yet. Add them under Languages > Languages." | Add at least one language in Polylang first. |
| A restricted user has no Posts, Pages or Products menu. | The post type is not ticked on the profile. Menus of public post types that are not ticked are hidden. Tick the type and save. |
| "You do not have permission to access this page." on Add New or the list. | The post type is not ticked, or the address contains new_lang for a language the user may not use. Use the Add New link from the menu so that the user's own language is set. |
| "You do not have permission to edit item." when saving. | The save tried to put the item into a language the user may not use. The Languages box and Quick Edit only offer the user's languages. A hand-made request or a stale form can send another one. |
| "You do not have permission to add new term." or "...to edit term." | The taxonomy is not ticked, or the term (or the chosen language) is outside the user's languages. Tick the taxonomy, or ask for the term in an allowed language. |
| A shop manager cannot edit a variation or product variations disappear. | The parent product is in another language or its post type is not ticked. Variations follow the parent product. A WooCommerce AJAX request ends with -1 (HTTP 403) and a REST request answers rest_forbidden. |
| The user cannot choose "Show all languages" in the admin bar. | By design: the item is removed for restricted users, also when no language is ticked. Choose another language from the list. |
| A user cannot edit translated content and the profile shows "A language assigned to this user no longer exists." | The language was deleted in Polylang. Open the user and choose a language, then save. The Users list shows "Language removed". |
| A translation was created but is not linked to the original. | The link is skipped when the language is not one of the user's, the source is another post type, the user cannot read the source, or the source already has a translation in that language in another post. Link it from an administrator account in Polylang's translations box. |
| Saving the user did not change the rules. | The save needs manage_options, permission to edit that user and this plugin's own nonce. A form that was open a long time can fail the nonce check: reload and save again. Another plugin that replaces the profile form can leave the box out. |
| A restricted user created a page or post through the REST API in a post type that is not ticked. | A known limit, see Known limits. Use the admin screens, or add your own permission check, for example in WordPress's rest_pre_insert_<post_type> filter (it runs for new and updated items). |
| No "Licence" card, or updates do not appear. | Polylang must be active for the plugin to register the licence client. Open Plugins → WpExperts Hub Licences and activate your key. Cached answers last up to 12 hours: use Dashboard → Updates → Check again after activating. |
10. FAQ
Which users can be restricted?
Any logged-in user who does not have the Administrator role and is not a super admin: Editors, Authors, Shop Managers, Contributors and custom roles. Administrators are never restricted.
Can a user have more than one language?
Yes, since 2.7.0. Tick as many languages as you like. Earlier versions showed a drop-down that saved a single language.
What happens when no language is ticked?
With Restricted chosen, the user may use every language (including languages added later) but only the ticked post types and taxonomies. With Full access chosen the user is not restricted at all, and the saved choices are kept in case you switch back.
I upgraded from an earlier version. Do I have to do anything?
No. A user that had a language assigned stays restricted to it, and a user without one stays unrestricted. Open and save a profile to switch it to the new mode setting.
Can a restricted user edit content through the REST API or Quick Edit?
Only content inside their rules. The rules are applied as WordPress capabilities, so the block editor, the WordPress and WooCommerce REST APIs, Quick Edit and bulk actions respect them. Creating a brand-new post of a post type that is not ticked through the REST API is not blocked by this plugin (see Known limits).
Can translators create translations of existing posts?
Yes, in any of their own languages. The plugin links the new item to the original. Items in other languages stay read-only.
Does it work with WooCommerce products?
Yes. Tick Products and the product taxonomies for a Shop Manager, and enable product translation in Polylang (or use Polylang for WooCommerce). Orders and coupons are not language-restricted. Variations follow their parent product.
Can I leave one post type open in all languages?
You can add it to the poly_user_open_access filter. That is reliable for post types that Polylang does not translate. For a translated post type, WordPress's edit, delete and publish checks still compare the language (see Leaving a post type open).
Which post types are listed?
Public post types except Media. Media is never blocked by this plugin. If Polylang translates media, the language rule applies to it.
How do I set up many users at once?
Use WP-CLI, for example wp polylang-user-manager set editor_fr --languages=fr --post-types=post,page --taxonomies=category. wp polylang-user-manager clear <user> gives the user full access again and list shows who has rules.
A language I assigned was deleted. What happens?
The user stays restricted. If it was their only language they cannot edit translated content until you choose a language again. The profile and the Users list flag it.
What is removed when I delete the plugin?
The users' Polylang access settings and the unused table older versions created. The licence options remain, so deactivate your licence first.
Does the plugin work without a licence?
Yes. The code never checks the licence before enforcing rules. The licence key is used for the update information and the update package.
How do I activate my licence and get updates?
Open Plugins → WpExperts Hub Licences, paste the key from your purchase email (also in My Account → Downloads on wpexpertshub.com) and click Activate licence. New versions then appear on the normal Dashboard → Updates screen.
11. Changelog
2.7.0 - 2026-10-06
- New: choose any number of languages per user (checkboxes instead of a single drop-down). Saving the Edit User screen used to keep only one language of a multi-language user.
- New: "Full access" / "Restricted" mode. A restricted user without a language may use every language but only the ticked post types and taxonomies.
- New: redesigned "Polylang access" box with a live summary, Select all / Clear, flags and a hint for post types and taxonomies Polylang does not translate.
- New: Users list column, "Restricted by Polylang" / "Not restricted by Polylang" views and language filter.
- New: read-only access summary on the user's own profile and a dismissible notice on the dashboard and list screens.
- New: menus, "+ New" items and list screens of post types and taxonomies the user may not use are hidden or refused.
- New: Quick Edit and bulk actions stay available on content the user may edit (every item is checked).
- New: WP-CLI commands
list,setandclear; filterpolylang_user_manager_access. - Security: product variations of other-language products could be edited, added and deleted by a restricted shop manager (REST API and the classic product screen's AJAX). They now follow their parent product.
- Security: "?lang=0", "?lang=all" or a language id in the URL unlocked the language filter and listed other-language content.
- Security: the profile fields are only saved with this plugin's own nonce. Other plugins firing the same profile hooks used to clear a user's restrictions.
- Fix: the stored language filter is corrected before Polylang reads it, so the first request of a restricted user is already filtered; an invalid filter request keeps the current allowed language.
- Fix: the block editor linked new translations using the user's first language, so translations in a second language were never linked.
- Fix: Media is no longer offered as a restrictable post type (it was never enforced), and its edit screen is no longer refused.
- Fix: the unused database table is no longer created; it is removed on uninstall together with the plugin's user meta.
- Fix: a language assigned to a user and later deleted is flagged instead of silently locking the user out.
2.6.0 - 2026-09-27
- Security - New licence client: licence data is exchanged as JSON only (the old client unserialized server responses) and a failed server request can no longer cause a fatal error.
- Enhancement - One "Plugins → WpExperts Hub Licences" screen manages the licences of all WpExperts Hub plugins (previously only one plugin's licence page could be opened when several were installed). Existing activations are kept.
- Enhancement - Updates are delivered through the standard WordPress update screen for sites with an active licence; "Email me my key" added.
- Security: restrictions are now enforced with capabilities, so other-language content can no longer be edited or deleted through the REST API (block editor, WooCommerce REST), quick edit, bulk edit/trash or AJAX.
- Security: the translation-link AJAX request now checks a nonce, the edit capability and the user's language.
- Fix: the saved language was not preselected on the Edit User screen, so saving the profile again removed the restriction.
- Fix: the plugin could stay inactive depending on plugin load order; it now starts after Polylang is loaded.
- Fix: changing a post to another language through the Languages box moved the post before the edit was refused; the change is now blocked first.
- Fix: the language drop-down removed English instead of the languages the user may not use.
- Fix: terms edited on the Edit Term screen (term.php) were not checked; term actions on the taxonomy screens now follow the rules.
- Fix: edit-tags.php requests with an action (delete, search) were redirected and lost.
- Fix: post types that Polylang does not translate were fully blocked for restricted users.
- New: Polylang's admin language filter is locked to the user's language; new posts/terms (including REST) get the user's language.
- Performance: removed a transient deletion that ran on every get_terms() call.
- Input sanitising and output escaping throughout; PHP 8.x compatibility; tested with WordPress 7.1 and Polylang 3.8.
2.5
- Previous release.
12. Support
Email support@wpexpertshub.com. Please include:
- Your order ID.
- The plugin version (this page describes 2.7.0), your WordPress version, your PHP version and your Polylang version.
- The role of the user, what you ticked on their Polylang access box, and the exact message you see.
- What you already tried. A screenshot of the Users list column and of the profile box helps.
More plugins and documentation are at wpexpertshub.com.