Migrating a codebase is where AI assistants either earn their keep or burn your entire sprint — these prompt templates force the model to show its work instead of guessing.
Prompt Templates for Code Migration: Ready-to-Use Templates
I've spent the last year migrating legacy AngularJS apps to React and Java 8 services to Kotlin, and I've landed on a set of prompt templates that consistently produce better migration code than ad-hoc requests. These work across Claude, GPT-4, Gemini, and DeepSeek, though I'll note where one model outshines the others. The core problem isn't the LLM's coding ability — it's that generic prompts like "convert this to TypeScript" produce generic, broken output. These templates fix that by forcing the model to reason about dependencies, edge cases, and verification before writing a single line.
Why Generic Prompts Fail Here
When you ask an LLM to "migrate this file," it does exactly what you asked — it migrates the file in isolation. It ignores the import graph, the test suite, the build config, and the runtime environment that file lives in. The result is code that compiles in the model's imagination but breaks your build because a dependency changed its API signature.
The second failure mode is hallucinated APIs. A model trained on React 18 documentation will happily generate createRoot calls when your project still uses ReactDOM.render. Generic prompts don't tell the model what version constraints apply, so it defaults to whatever it saw most recently in training data. These templates pin down versions, constraints, and verification criteria so the model can't drift.
Template 1: The Constraint-First Migration Prompt
You are migrating a single file from [SOURCE_LANGUAGE] to [TARGET_LANGUAGE] within an existing project.
Project context:
- Target language version: [TARGET_VERSION]
- Target framework: [FRAMEWORK_NAME and VERSION]
- Build system: [BUILD_SYSTEM, e.g., Vite, Webpack, Maven]
- Existing dependencies relevant to this file: [LIST DEPENDENCIES]
The file to migrate:
```[SOURCE_LANGUAGE]
[PASTE FULL SOURCE FILE]
Migration rules:
- Do NOT change the public API or function signatures of exported functions.
- Use only the dependencies listed above. Do not introduce new packages.
- Preserve all error handling and logging behavior exactly.
- If a source-language construct has no direct equivalent, explain the workaround in comments.
Expected output format:
- The complete migrated file in one code block.
- A list of behavioral changes you made, with reasons.
- A list of test cases from the original that might break, with explanations.
Do not output anything except the migrated file, the change list, and the test risk list.
**Placeholder breakdown:** `SOURCE_LANGUAGE` and `TARGET_LANGUAGE` are obvious. `TARGET_VERSION` matters more than you think — a model defaults to the latest version unless you pin it. `FRAMEWORK_NAME` and `BUILD_SYSTEM` prevent the model from generating code that conflicts with your toolchain. `LIST DEPENDENCIES` is where you paste your `package.json` or `pom.xml` dependencies — this is the single biggest accuracy lever. The rule about not changing public APIs is critical; LLMs love to "improve" your function signatures during migration, which silently breaks every caller.
## Template 2: The Incremental Migration Prompt
```text
You are migrating a module incrementally. The module is [MODULE_NAME] in [PROJECT_NAME].
Current state:
- The module has [NUMBER] files. [X] are already migrated to [TARGET_LANGUAGE].
- The remaining files are in [SOURCE_LANGUAGE].
- The already-migrated files use these patterns: [LIST PATTERNS, e.g., "async/await instead of promises", "named exports only"]
Here is the next file to migrate:
```[SOURCE_LANGUAGE]
[PASTE FILE]
Here are the imports this file uses from already-migrated files:
[PASTE IMPORT STATEMENTS FROM MIGRATED FILES]
Constraints:
- Match the coding patterns of the already-migrated files, not the source files.
- Update the import statements in this file to use the migrated versions of dependencies.
- If this file exports anything consumed by unmigrated files, keep the export signature in [SOURCE_LANGUAGE] compatible form.
Output:
- Migrated file.
- A list of import statements that need updating in OTHER unmigrated files that import from this one.
- Any circular dependency risks you notice.
This is step [N] of [TOTAL_STEPS]. Do not migrate anything else.
This one shines with Claude and Gemini because they handle multi-file context better. The key insight here is that you're not asking for a one-shot migration — you're asking the model to respect the partial state of your codebase. The "import statements that need updating" output is gold; it turns the model into a migration tracker that surfaces dependencies you might have missed.
## Template 3: The Edge-Case Migration Prompt
```text
You are migrating code that uses [SPECIFIC_PATTERN_OR_EDGE_CASE, e.g., "dynamic require() calls", "reflection-based dependency injection", "global state mutations"].
Source code:
```[SOURCE_LANGUAGE]
[PASTE FILE]
This code relies on runtime behavior that may not translate directly to [TARGET_LANGUAGE]. Specifically:
- [DESCRIBE EDGE CASE 1, e.g., "the require() path is computed at runtime"]
- [DESCRIBE EDGE CASE 2, e.g., "the DI container scans class names from strings"]
Your task:
- Identify which parts of this code depend on the edge-case behavior.
- For each dependency, propose a migration strategy. Choose ONE of: a. Direct translation (if a target-language equivalent exists) b. Refactoring (if the behavior needs to change) c. A compatibility shim (if you must preserve behavior exactly)
- Implement your chosen strategy in the migrated code.
- For any compatibility shim, provide a separate code block with the shim implementation.
Output format:
- Migrated file with inline comments marking each edge-case decision.
- A separate "Decisions" section listing each edge case, your chosen strategy, and why.
If you cannot preserve behavior without a shim, say so explicitly. Do not silently drop functionality.
This template is for the migration horror stories — dynamic imports, monkey-patching, reflection. DeepSeek handles this surprisingly well because it reasons about trade-offs more explicitly. The "compatibility shim" option is the escape hatch that prevents the model from either breaking your code or refusing to migrate it. I've found this template catches about 80% of the runtime errors that the other two templates miss.
## How to Adapt These for Your Own Codebase
First, build a project-context block once and reuse it in every prompt. A file with your language versions, build commands, and dependency list — paste it into every migration prompt. This cuts down on model guessing more than any other single change.
Second, migrate in dependency order. Run the incremental template on leaf files first, then work up the import graph. You'll get fewer cascading errors, and the model has more migrated context to match against.
Third, always ask for the "test risk list" output and actually check it. The model will often flag tests that break due to subtle behavioral differences. I've caught silent type coercion bugs this way that would have shipped to production.
Fourth, when you hit a file the model keeps getting wrong, break it into smaller pieces. Migrate the data structures first, then the functions that use them. The constraint-first template works better on smaller inputs.
## Do These Prompts Work With Any LLM?
Yes, but with caveats. GPT-4 and Claude produce the most reliable output for the constraint-first and incremental templates. Gemini handles the incremental template well but sometimes ignores version constraints. DeepSeek is the best for edge-case migrations but occasionally produces overly clever solutions that need review. The templates work because they constrain the problem space, not because of any single model's strengths. If you're using a smaller or older model, add the phrase "If you are unsure, state your assumptions before writing code" to any template — it forces the model to expose its reasoning gaps.
The one adjustment that improves these prompts the most: always include the exact dependency versions and the build command for your project. Every other improvement is secondary to that.