---
title: "Migrating from Polylang to PerfLocale - PerfLocale"
description: "Step-by-step guide to importing Polylang post and term translation groups into PerfLocale. Admin UI and WP-CLI paths, safety guarantees, verification."
canonical: "https://perflocale.com/docs/migration/polylang/"
source: "https://perflocale.com/docs/migration/polylang/"
format: "markdown"
---
# Migrating from Polylang

PerfLocale’s Polylang importer reads the `post_translations` and `term_translations` taxonomies and rebuilds your translation groups as native PerfLocale rows. Polylang’s data is read-only during the import - nothing is touched in its tables.

If you haven’t already, start with the [migration overview](https://perflocale.com/docs/migration/) for the shared pre-flight steps (backup, install PerfLocale, add languages).

## What’s imported

-   **Post translations** - Polylang stores translation groups as serialised PHP arrays in the `description` field of each `post_translations` term. The importer unserialises those, looks up each post by ID, and rebuilds the group in PerfLocale.
-   **Term translations** - same approach with the `term_translations` taxonomy: categories, tags, and any custom taxonomies Polylang was tracking.

## What’s _not_ imported

-   **String translations.** The Polylang importer only handles posts and terms. If you use _Polylang String Translations_ (the companion plugin or pro addon) for theme/plugin UI strings, those aren’t carried over. After the migration, scan the site with [`wp perflocale strings scan`](https://perflocale.com/docs/wp-cli/#scan-for-strings) to rebuild the string catalogue, then translate the strings again in the PerfLocale admin. For most sites this is a handful of strings, but budget time for it.
-   **Menus & widgets.** Polylang uses a different mechanism for menu / widget language-scoping (per-location menu variants). PerfLocale’s menu model is translation-group based, so menus need to be rebuilt post-import.
-   **Language-switcher widget configuration.** Polylang’s widget settings don’t carry across - configure PerfLocale’s [Language Switcher](https://perflocale.com/docs/language-switcher/) block, widget, or shortcode from scratch.
-   **Custom language flags and display names.** Re-add them in _PerfLocale → Languages_ when you create the matching PerfLocale language.

## Pre-flight checks

### Polylang doesn’t have to be active

The importer works both with Polylang active and with Polylang deactivated. Detection is two-layered:

1.  If the `language` taxonomy is registered (Polylang is active), the importer uses it.
2.  If Polylang is deactivated but its data remains in the database (`language` terms in `wp_term_taxonomy`), the importer detects that and proceeds anyway.

Practically: if you already switched off Polylang, the migration still works — the importer detects the _data_, not the plugin.

Leave it off, though. PerfLocale refuses to start while Polylang is active, so re-activating Polylang to help with verification stops PerfLocale instead: you get the admin notice _“PerfLocale: Cannot run while Polylang is active”_ and no plugin screens at all. Verify from PerfLocale’s own admin and the front end.

### Match your language codes

The importer maps Polylang’s language **slug** (e.g. `fr`, `de`) to a PerfLocale language using:

1.  The importer reads each Polylang language term’s authoritative WordPress locale (stored serialized in the term’s description, e.g. `pt_BR`) and matches it to a PerfLocale language whose `locale` is the same after normalizing case and hyphens-vs-underscores (so Polylang `pt-br` matches PerfLocale `pt_BR`, and `pt_BR` never collapses into `pt_PT`).
2.  If no locale is recorded, it falls back to an exact slug match (Polylang slug equals the PerfLocale language slug). There is no prefix/starts-with matching - add a PerfLocale language whose locale or slug matches exactly.

If your Polylang install used full locale slugs like `en_gb`, add a PerfLocale language with the matching slug exactly. Unmatched languages are reported as warnings (“No PerfLocale language match for Polylang slug `<slug>`”) and their content is skipped.

## Run the import

### From the admin

1.  Go to **PerfLocale → Settings → Export & Import**.
2.  Scroll to “Import from Polylang”. If the button isn’t shown, Polylang data wasn’t detected - the `language` taxonomy doesn’t exist and there are no `language` terms in the DB.
3.  Click **Import from Polylang**. Confirm the prompt.
4.  On completion the page reloads with a count of posts and terms imported.

### From WP-CLI

```
# Confirm Polylang data is detected.
wp perflocale migrate polylang --dry-run

# Run the real import (prompts for confirmation).
wp perflocale migrate polylang

# Skip the prompt when scripting.
wp perflocale migrate polylang --yes
```

## Verification

1.  **Post counts match.** Compare the post-translation counts reported by the importer against your expected counts (Polylang’s admin → Languages tab shows totals per language).
2.  **Spot-check a post.** Open any multilingual post. The PerfLocale sidebar should list every language that had a translation in Polylang.
3.  **Check terms.** Confirm translated categories / tags are linked in the PerfLocale translation column.
4.  **Visit translated URLs.** Polylang’s URL prefix convention (e.g. `/fr/`) is matched by PerfLocale’s default `slug`\-based prefix, so existing URLs should continue resolving. Browse a handful and confirm they render the right language.
5.  **Rebuild strings.** If you used Polylang String Translations, run `wp perflocale strings scan` (defaults to the active theme) and `wp perflocale strings scan --dir=wp-content/plugins`, then work through _PerfLocale → Strings_ to re-translate them.

## Re-running the import

The importer is **idempotent**. For each Polylang translation term, it looks for an existing PerfLocale group that already contains any of the posts listed - if found, it reuses the group and adds missing siblings. Already-linked siblings at the same language/group are skipped. If your first run warned about a language you hadn’t added yet, add it and re-run; the new language’s content fills in without duplicating the rest.

## Rollback

No automatic rollback. The safe paths:

-   **Restore from backup** - the cleanest undo.
-   **Reactivate Polylang** - Polylang’s data is untouched, so reactivating restores its behaviour. PerfLocale’s newly-created translation groups will sit alongside Polylang’s without interfering (PerfLocale only acts when its own data is present and the plugin is active).

## Troubleshooting

### “Polylang language taxonomy not found”

Either Polylang was never set up on this site, or its data was deleted. Check for `language` rows in `wp_term_taxonomy`: `SELECT COUNT(*) FROM wp_term_taxonomy WHERE taxonomy = 'language';`. If the count is zero, there’s nothing to migrate.

### The import timed out

As of 1.0.0, large migrations from the admin UI automatically queue via [background jobs](https://perflocale.com/docs/background-jobs/) and complete in the background. If the queue path isn’t available on your host, switch to WP-CLI - admin-UI requests are bounded by PHP’s request timeout; WP-CLI isn’t.

### Memory on very large sites

The Polylang importer fetches translation terms in batches rather than loading every `post_translations` / `term_translations` row in one query. It first lists the `term_id` column (cheap), chunks it by 100 by default, and only then reads the `description` payload (the serialised PHP array of `lang_slug => post_id`). Because `maybe_unserialize` expands every row into N PHP arrays in memory, this is the real lever for peak-memory tuning.

The filter is symmetric with the WPML one:

-   **Tight memory budget**: drop to `25` for more, smaller fetches.
-   **Plenty of RAM, latency-sensitive**: raise to `500` for fewer SQL roundtrips.

```php
add_filter( 'perflocale/migration/polylang/batch_size', static fn(): int => 250 );
```

Clamped to `10–1000`; default `100`. See [`perflocale/migration/polylang/batch_size`](https://perflocale.com/docs/hooks/#perflocale-migration-polylang-batch-size) in the hooks reference.

### My translated strings didn’t come across

Expected - the Polylang importer doesn’t handle strings. See “What’s not imported” above.

## Next steps

-   Run the string scanner to rebuild the catalogue: see [WP-CLI: scan for strings](https://perflocale.com/docs/wp-cli/#scan-for-strings).
-   Configure [language fallbacks](https://perflocale.com/docs/language-fallbacks/).
-   See the [PerfLocale vs Polylang feature comparison](https://perflocale.com/compare/polylang/) for what’s available in PerfLocale that Polylang charges Pro for.
