# Migrate an existing backend to Encore

This guide is for coding agents. Follow it to move an existing backend to [Encore](https://encore.dev). It works for any source framework, for example Express, Fastify, Hono, NestJS, Koa, Gin, Echo, Chi, FastAPI, Django or Rails.

To use it, tell your agent: `Read https://encore.dev/agents/migrate.md and follow it to migrate <repository URL or path> to Encore.`

The workflow:

1. Set up an empty Encore app next to the source.
2. Inspect the source repository.
3. Identify its dependencies and infrastructure.
4. Agree on a migration plan with the person you're working for.
5. Migrate and verify one part at a time.

Progress is tracked in files in the Encore app's repository, so another session can pick up where this one stopped.

## Rules

- **Source:** Never modify the source. Read its files, but don't edit, delete or write to them, and never run destructive commands against its databases, queues or other systems. It's the original to compare against.
- **Order:** Run the steps in order. Skip a step when its check shows it's already done, so the guide is safe to run again.
- **Commands:** The commands below use flags that keep them from prompting for input. Run long-running commands, like `encore run`, in the background.
- **Failures:** If a step fails, stop. Show the person the command, its error output and what you think went wrong.
- **Interactive prompts:** If a command prompts anyway, never answer it for the person (account, organization, credentials, confirmations). Ask them.
- **Side effects:** Ask before any HTTP request that could change state in the source system. Don't deploy unless the person asks.
- **Evidence:** Mark something as migrated only with fresh evidence from this session, such as passing tests.

## Inputs

Take these from the person's request. Ask only for what you can't work out.

- **Source:** A GitHub URL, `owner/repo`, or a local path.
- **Target language:** One of the languages Encore supports. `encore app create --help` lists them under `--lang`. If the person didn't name one, use the source's language when Encore supports it. Otherwise, ask them, listing the supported languages.
- **App name:** `<source-name>-encore`, unless they gave one.
- **Mode:** By default, stop for the person's confirmation at the points marked **Confirm** below. If they ask you to run straight through ("just migrate it", "don't ask"), skip those stops. Instead, pick sensible defaults, list each one for the person before you write code, and record it under `## Decisions` in `migration-plan.md`.

## 1. Set up

### Install the Encore CLI

Skip this step if `encore version` works. Otherwise install it:

- **macOS:** `brew install encoredev/tap/encore`
- **Linux:** `curl -L https://encore.dev/install.sh | bash`. The binary goes to `~/.encore/bin`. Add that to `PATH` if `encore` isn't found.
- **Windows (PowerShell):** `iwr https://encore.dev/install.ps1 | iex`

Then check that `encore version` works. Check the prerequisites for the target language too. The quick start lists them: https://encore.dev/docs/quick-start.md

### Ask the person to log in

Skip this step if `encore auth whoami` shows a logged-in user. Logging in registers the app on Encore Cloud, and only the person can do it. Ask them to run `encore auth login`, which opens a browser where they log in or sign up. Without a browser, they can set the `ENCORE_AUTH_KEY` environment variable to an Encore auth key instead, and the CLI logs in with it. Continue with the next steps while they log in.

### Get the source

For a local path, use it as it is.

For a GitHub repository, skip this step if a directory with that name in the current directory already holds it. Otherwise clone it into the current directory with `git clone --depth 1 https://github.com/<owner>/<repo>.git`. Set `GIT_TERMINAL_PROMPT=0` so that a private repository fails instead of waiting for a password. If it fails because the repository is private, run `gh repo clone <owner>/<repo> -- --depth 1`.

### Create the Encore app

Create the app next to the source, in the same parent directory, never inside it. Skip this step if `<app-name>/encore.app` already exists.

**Login:** Before you create the app, check that `encore auth whoami` shows the person's user. If it doesn't yet, remind them to finish logging in. If they don't want to log in, add `--platform=false` to the command below. They can link the app to Encore Cloud later (the `encore-deploy` skill covers this).

Create an empty app:

```bash
encore app create <app-name> --lang=<lang> --llm-rules=<rules>
```

**Rules:** For `<rules>`, `encore app create --help` lists the supported agents, for example `claudecode` for Claude Code and `vscode` for GitHub Copilot in VS Code. Use the one for the agent you are, or `agentsmd` if yours isn't listed. This writes Encore's rules into the app, and for some agents it also configures Encore's local MCP server.

**Organization:** If the command asks which organization to create the app in, ask the person, then run it again with `--org=<org>`.

`encore app create` prints the commands for running and testing the app. Note the test command for step 5.

### Install Encore's agent skills

In the app directory, run:

```bash
npx skills add encoredev/skills --skill '*' -a <agent> -y
```

Set `<agent>` to your agent's ID in the skills CLI, for example `claude-code`, `cursor`, `codex` or `github-copilot`.

The `encore-migrate` skill is the full version of this guide's workflow, with more detail on each phase. The other skills cover each Encore primitive (APIs, databases, auth and so on) for each language. Use the matching skill for each part as you migrate it. If the skills aren't loaded in this session, find each `SKILL.md` under the app directory and read it before you use it. If there's no skill for the target language, use the docs instead.

### Connect Encore's local MCP server

The MCP server gives you the app's services, APIs, database schemas and traces, and can call its endpoints. If app creation didn't configure it for your agent, add a stdio MCP server that runs `encore mcp run --app=<app-id>`, with `<app-id>` being the `id` field in `<app-name>/encore.app`. In Claude Code, for example: `claude mcp add --transport stdio encore-local -- encore mcp run --app=<app-id>`. Details: https://encore.dev/docs/mcp.md

Most agents load new skills and MCP servers only when a session starts. If that applies to you, don't stop. Tell the person and continue without them. They can restart you in the app directory later, and once `migration-plan.md` exists, the migration resumes from it.

From here on, work in the app directory and refer to the source by its path, for example `../<source>`.

## 2. Inspect the repository

If the app directory already has a `migration-plan.md`, a previous session started this migration. Go to [Resume in a new session](#7-resume-in-a-new-session).

Read the source and list everything it contains. Start with the package manifests (`package.json`, `go.mod`, `requirements.txt`, `Gemfile`, `pom.xml`), the entry points and routers, configuration and `.env.example` files, SQL migrations, `docker-compose.yml`, CI workflows and any infrastructure code such as Terraform or Kubernetes manifests.

| Category | What to record |
| --- | --- |
| Services and modules | Bounded contexts, separately deployed units, route groups |
| API endpoints | Method, path, handler, request and response types |
| Databases | Engine, tables, schemas, migration files, ORM |
| Messaging | Topics or queues, publishers, subscribers, message types |
| Scheduled jobs | Schedules and their handlers |
| Auth | Strategies, token validation, sessions, where it's enforced |
| Secrets and config | Every environment variable, and which ones are sensitive |
| Global HTTP behavior | CORS, path prefixes, global middleware, custom error bodies |
| Tests | Test files, what they cover, the test framework |
| Frontend | Components, static files and client code, which are out of scope |

**Frontend code:** Mark it out of scope. Only the backend moves to Encore. Some frameworks also hold server code that could move: Next.js `pages/api/` and `app/**/route.ts`, Remix loaders and actions, Nuxt `server/`, SvelteKit `+server.ts` and `+page.server.ts`, Astro endpoints in `src/pages/*.ts`, and Gatsby Functions in `src/api/`. If you find any, **Confirm:** ask the person whether to migrate all of it, some of it (they pick) or none of it.

**No backend:** If the repository has no backend code to migrate, for example because it's only a frontend site or a library, stop. Tell the person what you found and don't build anything.

## 3. Identify dependencies and infrastructure

Map each dependency and piece of infrastructure to its Encore equivalent. Encore provisions these from declarations in code, locally with `encore run` and in the cloud when deployed. For how each one works in the target language, find its page in the docs index: https://encore.dev/llms.txt lists every docs page as markdown, grouped by language.

| Source uses | Encore equivalent |
| --- | --- |
| Modules, microservices | Services |
| Routes, controllers, handlers | Typed API endpoints |
| Webhooks, raw HTTP, file streams | Raw endpoints |
| PostgreSQL, MySQL, SQLite, ORMs | SQL databases (PostgreSQL) |
| Queues and event buses (SQS, RabbitMQ, Kafka, Redis pub/sub) | Pub/Sub |
| Scheduled jobs (cron, node-cron, Celery beat) | Cron jobs |
| S3, GCS, file uploads | Object storage |
| Redis caching, rate limits | Caching |
| Credentials in environment variables | Secrets |
| Auth middleware (JWT, sessions, API keys) | Auth handler |
| Other middleware | Middleware |
| CORS settings | `global_cors` in `encore.app` (https://encore.dev/docs/cors.md) |
| Tests | `encore test` |

Encore's SQL databases are PostgreSQL, so MySQL or SQLite schemas and queries need converting. Anything without an Encore equivalent, such as MongoDB, Elasticsearch or a third-party API, stays an external dependency that the app connects to with secrets. **Confirm:** ask the person how to handle each of those.

## 4. Agree on a plan

**Decisions:** Some choices affect every part of the migration. Raise them now:

- **Behavior target:** Reproduce the source exactly, or fix clear bugs? If the source implements a published contract, such as an OpenAPI spec, fixing toward the contract is usually right.
- **Error responses:** Typed Encore endpoints return errors as `{code, message, details}`, and validation failures are always HTTP 400. If clients depend on a different error body or status, either accept Encore's format or use raw endpoints for those routes.
- **CORS:** If the source enables CORS, carry it over as `global_cors` in `encore.app`.

**Migration units:** Group what you found into units. Use these signals in order: existing service or module boundaries, URL path prefixes, shared database tables, then shared types. Aim for 5 to 15 endpoints per unit. Make auth, secrets and standalone infrastructure (topics, cron jobs) units of their own.

**Order:** Sort the units by dependency: secrets and config, then databases, auth, units with no dependencies on other units, units that depend on those, Pub/Sub, and finally cron jobs. Within each tier, put the simplest unit first.

**Confirm:** Show the person a table of units (endpoints, database tables, other entities, complexity), the totals, and short before-and-after previews of two or three representative entities. Ask:

- Did you miss any services, endpoints or other entities?
- Should any units be split, merged, renamed or left out?
- What do they want for each decision above?
- Does the original run locally, and at which URL? This is optional. It lets you compare responses from the two apps.

Revise the plan until they confirm it. Then write the plan files described in [Progress files](#progress-files), and propose the first unit.

## 5. Migrate and verify one unit at a time

Repeat for each unit:

1. **Pick the unit:** Take the next `pending` unit in dependency order and say why it's next. **Confirm** that the person wants to start it, unless they've told you to keep going.
2. **Load it:** Read `migration-plan/<unit>.md`. Don't read the other units' detail files.
3. **Implement:** For each entity in the unit, write the Encore version using the matching skill and docs. Migrate its tests too. If it has no tests, note that in the detail file.
4. **Verify:** Validate each entity as described in the next section.
5. **Record:** Update each entity's status and the Validation Log in the detail file. Set the unit's status in `migration-plan.md` to `migrated` once every entity is done, or `in progress` while some aren't.
6. **Report:** Tell the person what you migrated and verified, and suggest the next unit.

### Verify

Use every check that applies:

- **Tests:** Run the test command `encore app create` printed, with `CI=true` set (for example `CI=true encore test`) so the test runner doesn't start in watch mode. The entity's tests must pass.
- **HTTP comparison:** If both apps run locally, send the same request to each and compare the status codes and the keys of the response bodies. Values that change per request, like IDs and timestamps, can differ. Ask before sending any request that changes state. If either app doesn't respond, ask the person to start it. Encore's MCP server can call endpoints and show traces for these checks.
- **Contract tests:** If the source can't run but the API has an external test suite (Hurl, Postman, Bruno, Schemathesis), run it against the Encore app. Separate failures caused by recorded decisions, such as the error format, from real gaps, and fix the gaps.

Mark an entity `migrated` only with evidence from this session: test output with the pass count and exit code, a matching HTTP comparison, or the person's explicit approval to skip validation. State exactly what you checked and what it showed. If you don't have enough evidence, mark it `manual validation needed`.

## 6. Finish

When every unit is `migrated`, `skipped` or `manual validation needed`:

1. Run the full test suite one last time.
2. Summarize for the person: units migrated, units skipped and why, and every entity marked `manual validation needed`, with the reason.
3. If the source has a frontend, offer to connect it to the new backend with a client generated by `encore gen client`. The docs index has the client generation page for each language.
4. Offer to deploy the app to Encore Cloud: https://encore.dev/docs/platform/deploy/deploying.md
5. Once the person is satisfied, suggest removing `migration-plan.md` and `migration-plan/`.

## 7. Resume in a new session

Decide where to continue from the files in the app directory:

- **No `migration-plan.md`:** Start at [Inspect the repository](#2-inspect-the-repository).
- **`migration-plan.md` but no `migration-plan/` directory:** Inspection is done. Finish [Agree on a plan](#4-agree-on-a-plan) by writing the detail files.
- **Units with status `pending` or `in progress`:** Read `migration-plan.md` only. Report the status, for example "3 of 7 units migrated, next suggested: billing", and continue with [Migrate and verify](#5-migrate-and-verify-one-unit-at-a-time).
- **Every unit `migrated`, `skipped` or `manual validation needed`:** Go to [Finish](#6-finish).

Evidence from an earlier session doesn't count. Run the checks again for anything you mark `migrated` now.

## Progress files

Write these at the root of the Encore app. Use these formats exactly, so that the `encore-migrate` skill and later sessions can read them.

`migration-plan.md`:

```markdown
# Migration Plan

## Source System
- **Path:** <source path>
- **URL:** <source local URL, or "not running">
- **Framework:** <detected framework>
- **Language:** <detected language>

## Frontend (Out of Scope)
- **Detected:** <Yes/No>
- **Directories:** <frontend directories, or "None">
- **Framework:** <frontend framework, or "N/A">
- **Note:** <framework server code that was included in migration units>

## Target System
- **Path:** <Encore app path>
- **URL:** http://localhost:4000
- **Type:** <Encore SDK, for example Encore.ts>

## Decisions
- **Behavior target:** <reproduce source exactly | fix bugs toward <contract>>
- **Error format:** <Encore native | raw endpoints for <routes>>
- **CORS:** <source setting → encore.app global_cors>
- <any other decision or default>

## Migration Units

| Unit | Endpoints | DB Tables | Other | Complexity | Status |
|------|-----------|-----------|-------|------------|--------|

## Dependency Order
1. <units in migration order>
```

`migration-plan/<unit>.md`, one per unit. Leave out sections that don't apply:

```markdown
# Migration Unit: <unit>

## Source
- **Files:** <source files in this unit>
- **Depends on:** <other units, with their current status>

## Endpoints
| Endpoint | Method | Path | Status | Notes |
|----------|--------|------|--------|-------|

## Database
| Table | Complexity | Status | Notes |
|-------|------------|--------|-------|

## Tests
- **Source tests:** <test files and count>
- **Migrated:** <count of migrated tests>

## Validation Log
| Entity | Tests | HTTP Match | Evidence | Status |
|--------|-------|------------|----------|--------|
```

- **Status values:** `pending`, `in progress`, `migrated`, `skipped`, `manual validation needed`
- **Complexity values:** `Low` (direct Encore equivalent), `Medium` (needs restructuring), `High` (needs redesign)

## References

- **Docs index:** https://encore.dev/llms.txt lists every docs page as markdown, grouped by language.
- **Full migration skill:** https://raw.githubusercontent.com/encoredev/skills/main/encore/migrate/SKILL.md
- **All Encore skills:** https://github.com/encoredev/skills
- **Local MCP server:** https://encore.dev/docs/mcp.md
- **Creating a new app instead:** https://encore.dev/agents/getting-started.md
