How to Create a .pot File for a Theme or Plugin

You just finished a WordPress plugin you want to share with the world. The code works, the features are solid, and someone in Germany emails asking how to translate it into German. You point them to the languages folder and realize there is nothing there. No template, no strings, no way for a translator to even know what needs translating. Your plugin is technically "translation-ready" in name only.
Every translatable WordPress project starts with one file: the .pot template. Before anyone can produce a German, French, or Japanese version, you have to create a POT file that lists every user-facing string in your code. Skip this step and translators are stuck reading your source line by line, guessing which strings are even meant to be visible.
This guide walks through the two halves of the job: preparing your source code so strings are discoverable, and then generating the .pot itself with three different tools. We will use WP-CLI, Poedit, and the legacy makepot/grunt approach, with real commands you can copy. By the end you will have a clean template ready to hand to any translator.
Step One: Mark Your Strings as Translatable
Before you can create a POT file, every string a user might see needs to be wrapped in a Gettext function. A scanner can only extract strings it recognizes, and it recognizes them by these function calls — plain string literals are invisible to it.
WordPress provides a family of localization functions, each for a slightly different context:
// Return a translated string
$label = __( 'Add to Cart', 'my-plugin' );
// Echo a translated string directly
_e( 'Your cart is empty', 'my-plugin' );
// Translate with context to disambiguate identical words
$verb = _x( 'Post', 'verb: to publish', 'my-plugin' );
// Translate AND escape for safe HTML output
echo esc_html__( 'Welcome back', 'my-plugin' );
The second argument in every call — 'my-plugin' — is the text domain. It groups all of a plugin or theme's strings under one namespace so WordPress knows which .mo file to load for them. Every translatable string in your project must share the exact same text domain, or some strings will never get translated.
Use _x() whenever a word is ambiguous. The English "Post" can be a noun or a verb, and many languages translate them differently. Context strings let translators tell them apart. And reach for the esc_html__() and esc_attr__() variants whenever the translated string is printed into HTML, so you stay safe and localized at the same time.
There are a few habits that keep your strings extractable. Always pass a plain string literal as the first argument — never a variable or a concatenation like __( 'Hello ' . $name, 'my-plugin' ), because the scanner reads source text statically and cannot resolve runtime values. For sentences with a number, use _n() so plural forms are captured correctly, and for sentences with a dynamic value, use sprintf() around a placeholder such as %s rather than gluing strings together. Consistency here is what makes the next step — generating the template — produce a complete, usable result.
Step Two: Declare the Text Domain in Your Header
WordPress needs two header fields to know where your translations live: the Text Domain and the Domain Path. Set them in your plugin's main file or your theme's style.css, and WordPress will automatically load the right .mo at runtime.
<?php
/**
* Plugin Name: My Plugin
* Description: A perfectly localized WordPress plugin.
* Version: 1.0.0
* Requires at least: 6.4
* Text Domain: my-plugin
* Domain Path: /languages
*/
The Text Domain must match the string you passed to every __() and _e() call — my-plugin here. The Domain Path tells WordPress which subfolder holds the translation files, relative to the plugin or theme root. The convention is /languages, and that is exactly where your generated .pot should live.
With strings wrapped and the header declared, your code is ready to scan. The broader picture of preparing a project for translation — including themes that are not your own — is covered in how to localize any WordPress theme even if you are not a developer.
How Do You Generate the .pot File? Three Methods
You generate a .pot by running a scanner over your source code that finds every Gettext call and writes the strings into a template. Here are three reliable ways to do it, from the modern default to the legacy approach.
Method 1: WP-CLI (Recommended)
The official, modern way to create a POT file is WP-CLI with the i18n command. It ships as part of WordPress core tooling, understands PHP and JavaScript strings, and produces a standards-compliant template in one line.
# Run from your plugin or theme root directory
wp i18n make-pot . languages/my-plugin.pot
# Add a text domain explicitly if auto-detection misses it
wp i18n make-pot . languages/my-plugin.pot --domain=my-plugin
# Scan only specific paths and exclude vendor folders
wp i18n make-pot . languages/my-plugin.pot --exclude=vendor,node_modules
The first argument is the source directory (. for the current folder), and the second is the output path. WP-CLI walks your files, extracts every wrapped string, records the source file and line number as a comment, and writes the .pot. It is fast, scriptable, and the right choice for any serious project.
The generated template includes helpful metadata you can tune with extra flags. Pass --headers to set the project name, version, and the translators' contact address, so the .pot header block is filled in correctly rather than left as placeholders. WP-CLI also extracts JavaScript strings from wp_set_script_translations() calls automatically, which matters for modern block themes and Gutenberg plugins where half the user-facing text lives in JavaScript rather than PHP. One command covers both worlds.
Method 2: Poedit (Graphical)
If you prefer a desktop app, Poedit can scan your source and generate the template through its interface. Open Poedit, choose to create a new translation from POT or scan sources directly, point it at your project folder, and configure the source keywords (__, _e, _x, esc_html__) under the catalog properties.
Poedit's source-scanning and .pot generation features live in its Pro edition, but the workflow is friendly for developers who would rather click than type commands. It is also a solid editor for the translation phase that follows. Poedit and several other desktop options are compared in top 5 free tools to edit and translate PO files on Mac and Windows.
Method 3: makepot / grunt (Legacy)
Before WP-CLI, the standard tool was the makepot.php script from the WordPress i18n-tools repository, often wired into a Grunt build task with grunt-wp-i18n. You will still meet it in older plugins and themes.
# Legacy makepot.php invocation
php makepot.php wp-plugin /path/to/my-plugin languages/my-plugin.pot
It works, but it is unmaintained relative to WP-CLI and slower to set up. Use it only when you are maintaining a legacy project that already depends on it; for anything new, prefer wp i18n make-pot.
Keep the Template Updated as Your Code Changes
A .pot is a snapshot of your strings at one moment. Every time you add a feature, change a label, or fix a typo in a visible string, the template goes stale and translators miss the new wording.
Make regeneration part of your release routine. Re-run wp i18n make-pot before every version bump, then merge the refreshed template into existing translations with wp i18n update-po or msgmerge so translators keep their finished work and only see the new gaps. Treat the .pot as a build artifact you regenerate, not a file you hand-edit.
A stale template causes a subtle, frustrating failure: new strings appear in English even on a "fully translated" site, because they were never in the .pot the translator worked from. Catching this is as simple as scripting the regeneration into your build, so the template can never drift behind the code. Some teams add a CI check that fails the build if regenerating the .pot produces a diff, guaranteeing the committed template always matches the current source.
Once translators return finished .po files, those still need compiling to the binary .mo that WordPress actually loads — and if you would rather skip the whole download-translate-recompile loop, a cloud tool can handle it end to end. SimplePoTranslate accepts your .pot directly, translates it with Context-Aware AI that understands what each string means in your interface, and returns a single ZIP with the .po, .mo, and more already built and named. Its Syntax Locking freezes placeholders like %s and %1$s so your variables never break in translation.
Wrapping Up
To create a POT file the right way, first make your code discoverable — wrap every visible string in __(), _e(), _x(), or an escaping variant, all sharing one text domain, and declare that domain plus the Domain Path in your header. Then generate the template with WP-CLI for new projects, Poedit if you prefer a GUI, or makepot only when maintaining legacy code.
Generate early, regenerate often, and never let your template fall behind your code. A current .pot is the difference between a plugin that is genuinely ready for the world and one that only claims to be.
Ready to turn your
.pottemplate into finished translations without the manual compile dance? Try SimplePoTranslate free — no credit card required. Upload your template on the free tier and download ready-to-ship translation files in minutes.