---
title: "Migration from other multilingual plugins - PerfLocale"
description: "Import your translations from WPML, Polylang, or TranslatePress into PerfLocale - admin UI, WP-CLI, pre-flight checks, and verification steps."
canonical: "https://perflocale.com/docs/migration/"
source: "https://perflocale.com/docs/migration/"
format: "markdown"
---
# Migration

PerfLocale ships with importers for the three most common multilingual plugins. Each one reads the source plugin’s data and writes equivalent rows into PerfLocale’s own tables - your original plugin’s data is left untouched, so you can re-run or abandon a migration at any point without losing work.

## Supported sources

-   [WPML](https://perflocale.com/docs/migration/wpml/) - posts, terms, strings.
-   [Polylang](https://perflocale.com/docs/migration/polylang/) - posts, terms. (String translations are not imported; see the page for details.)
-   [TranslatePress](https://perflocale.com/docs/migration/translatepress/) - posts (reconstructed from the string dictionary), strings, translated slugs.

Imports from qTranslate-X, Multisite Language Switcher, GTranslate, or other multilingual plugins are not currently bundled. If you’re on one of those, you can export via WPML’s importer first (many of them offer a WPML-compatible bridge) then run the PerfLocale WPML importer.

## Before you start

1.  **Back up your database.** The importer doesn’t modify the source plugin’s data, but it does write new rows into PerfLocale’s own tables, and a backup is always the right baseline before a structural change.
2.  **Deactivate the source plugin first.** PerfLocale refuses to start while WPML, Polylang or TranslatePress is active — you will see the admin notice _“PerfLocale: Cannot run while WPML is active. Please deactivate one of the two multilingual plugins to avoid conflicts. Your WPML translations stay in the database: after deactivating it, import them from Settings → Export & Import, or with `wp perflocale migrate`.”_ (on multisite the same notice appears in Network Admin when the other plugin was network-activated) and a matching critical result in _Tools → Site Health_, and neither the importer screen nor the `wp perflocale migrate` command will exist until you do. Deactivating is safe: it leaves every row in the database untouched, and the importer reads those tables directly rather than calling the other plugin.
3.  **Install and activate PerfLocale.** The importer reads the source plugin’s database tables, not its runtime, so everything it needs is still there.
4.  **Add every language you intend to migrate** in _PerfLocale → Languages_ before running the importer. Language matching is done by `slug` first (e.g. `fr` on both sides) then falls back to a `locale` prefix match (e.g. PerfLocale’s `fr_FR` against WPML’s `fr`). Any source language without a PerfLocale match is reported as an error and its content is skipped - the importer doesn’t auto-create languages for you, by design.
5.  **Decide whether to run the admin UI or WP-CLI.** For sites with more than a few hundred posts, use WP-CLI: it avoids PHP max-execution-time limits that the admin-request path can run into on shared hosting.

## How it works

Every importer follows the same shape:

1.  **Detection.** `can_import()` checks that the source plugin’s data exists (e.g. WPML’s `icl_translations` table, Polylang’s `language` taxonomy, TranslatePress’s `trp_original_strings` table). If not found, the import is refused with a clear error.
2.  **Language mapping.** Every language code in the source is matched to a PerfLocale language ID. Unmatched languages produce a warning and their content is skipped - the rest of the import proceeds.
3.  **Data migration.** Posts and terms are grouped into PerfLocale _translation groups_. Strings get hashed and inserted into `wp_perflocale_strings`, then their translations are linked per language.
4.  **Batched fetch.** All three importers chunk their SELECTs against the source plugin’s tables so peak memory stays bounded regardless of site size. Internal benchmark: **5,000 translation groups × 3 languages (15,000 link writes) completes under 32 MB peak delta**. Per-plugin tuning knobs: [WPML](https://perflocale.com/docs/migration/wpml/#memory-tuning), [Polylang](https://perflocale.com/docs/migration/polylang/#memory-tuning), [TranslatePress](https://perflocale.com/docs/migration/translatepress/#batch-size). The effective batch size for each is exposed in _Tools → Site Health → Info → PerfLocale_ under _“Migration importer batch sizes”_, so support threads can see the live value at a glance.
5.  **Idempotent re-runs.** If a translation group already exists for one of the items being imported, the importer _reuses_ that group and adds any missing siblings. Re-running after a partial failure fills the gaps instead of creating duplicates.

## Safety model

What the importer guarantees:

-   **Source data is read-only.** The importer never writes to WPML’s, Polylang’s, or TranslatePress’s tables. Even if something goes wrong mid-run, reactivating the source plugin gives you back your original setup unchanged.
-   **Pre-flight validation.** Language mapping is resolved before any data is written. If half your source languages don’t match PerfLocale languages, the importer tells you up front rather than writing partial results.
-   **Dry-run via WP-CLI.** `wp perflocale migrate <source> --dry-run` confirms the source is detected and would be importable, without writing anything.
-   **Disaster-recovery idempotent.** Each `translation_groups` row is created in a single SQL transaction together with a `(migration_type, source_key) → group_id` mapping in `perflocale_migration_source_map`. If your host restores a backup that wiped the groups table, re-running the import looks up the mapping first and reuses the existing group\_id rather than allocating a duplicate. TranslatePress gets the same protection via a `get_translation_in_language()` pre-check on the destination side. See the [reliability page](https://perflocale.com/docs/reliability/#dr-idempotency) for the full mechanism.
-   **Resumable.** Re-running the importer after a partial run completes the gaps; already-linked items are skipped.

What the importer _doesn’t_ guarantee (and you should know):

-   **No automatic rollback.** There is no “undo migration” button. If you want to abandon an import mid-way through verification, the cleanest path is: restore the database backup you took in step 1, or delete the newly-created translation posts manually from _Posts → All Posts_ in the admin while the source plugin is still active.
-   **Per-row, not per-import, transactions.** Each `create_group()` + source-map insert pair is one atomic transaction, but the whole importer is a sequence of those. If your host kills the PHP process halfway (timeout, OOM), partial data may be written. Re-running is safe and fills the gaps (source-map lookups + link UNIQUE constraints prevent duplicates), but it’s not all-or-nothing at the migration level.
-   **No count preview.** `--dry-run` only confirms that data _exists_; it doesn’t report “this will import 234 posts, 89 terms, 1 412 strings.” You’ll see the totals only after the run finishes.

### Force-restart after a deliberate DB restore

If you intentionally restore a backup to a known-good _pre-import_ state and want the next migration run to allocate _fresh_ translation\_groups rather than reuse stale mappings from the prior run, pass `--force-restart` to clear the source-map for that importer before the new run starts:

```bash
wp perflocale migrate wpml --force-restart --yes
wp perflocale migrate polylang --force-restart --yes
wp perflocale migrate translatepress --force-restart --yes
```

The flag is a no-op when the source-map is already empty. The CLI logs the number of rows cleared before the import begins.

**Caution:** `--force-restart` on a database whose `translation_groups` rows are still pointing at the source plugin’s data _will_ orphan those groups. Run it only after you’ve restored a clean pre-migration backup. The default (omit the flag) is the safer choice for almost every scenario.

## Running the import

### From the admin

Go to **PerfLocale → Settings → Export & Import**. The page shows a status badge for each source plugin whose data is detected on your site:

-   “Import from WPML” - visible when `wp_icl_translations` exists.
-   “Import from Polylang” - visible when the `language` taxonomy is present.
-   “Import from TranslatePress” - visible when `wp_trp_original_strings` exists.

Click the button, confirm the prompt, and wait for the page to reload with an import summary.

Large migrations don't time out: PerfLocale's [background-jobs system](https://perflocale.com/docs/background-jobs/) automatically queues big migrations (the threshold is configurable) and redirects you to **PerfLocale → Jobs** where progress is visible live. Small migrations still finish inline so you see the summary immediately.

### From WP-CLI

```
# Confirm the source is detected (no data written).
wp perflocale migrate wpml --dry-run

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

# Skip the confirmation prompt (for scripting / CI).
wp perflocale migrate wpml --yes
```

Replace `wpml` with `polylang` or `translatepress` as appropriate. Output:

```bash
$ wp perflocale migrate wpml
Migrating from wpml...
Success: Migration complete. Imported: 234 posts, 89 terms, 1412 strings.
```

## After the import

1.  **Spot-check translations.** Open a multilingual post in the standard editor and use the PerfLocale Translations panel to confirm all the expected language versions are linked.
2.  **Verify URLs resolve.** Visit `/fr/your-post/`, `/de/your-post/`, etc. and confirm the prefixed language URLs return the correct translated posts.
3.  **Check the hreflang tags.** View the page source of any translated post and confirm `<link rel="alternate" hreflang="…">` tags point at the correct sibling URLs.
4.  **Flush caches.** If you use an object cache or page cache, flush it - PerfLocale’s internal caches are invalidated automatically, but external caches aren’t.
5.  **Deactivate the source plugin.** Only after you’re confident the import succeeded. Keep a database backup for at least a week in case you need to roll back. The source plugin’s _data_ can stay in the database indefinitely - PerfLocale ignores it.

## Per-source details

Each source plugin has its own quirks - what’s imported, what isn’t, language-code edge cases, and specific verification steps:

-   [Migrating from WPML](https://perflocale.com/docs/migration/wpml/)
-   [Migrating from Polylang](https://perflocale.com/docs/migration/polylang/)
-   [Migrating from TranslatePress](https://perflocale.com/docs/migration/translatepress/)

For the CLI command reference, see [WP-CLI: `wp perflocale migrate`](https://perflocale.com/docs/wp-cli/#migrate).
