···34343535First, you can grab your entire repository in the from of a [CAR file](https://ipld.io/specs/transport/car/carv1/) by calling `com.atproto.sync.getRepo`. You can then upload those exact bytes to your new PDS through `com.atproto.repo.importRepo`. The new PDS will parse the CAR file, index all blocks and records, and sign a new commit for the repository.
36363737-Next, you'll need to upload all relevant blobs. These can be discovered by calling `com.atproto.sync.listBlobs` on your old PDS. For each blob, you'll need to download the contents through `com.atproto.sync.getBlob` and upload them to your new PDS through `com.atproto.repo.uploadBlob`.
3737+Next, you'll need to upload all relevant blobs. These can be discovered by calling `com.atproto.sync.listBlobs` on your old PDS. For each blob, you'll need to download the contents through `com.atproto.sync.getBlob` and upload them to your new PDS through `com.atproto.repo.uploadBlob`.
38383939Finally, you'll need to migrate private state. Currently the only private state held on your PDS is your preferences. You can migrate this by calling `app.bsky.actor.getPreferences` on your old PDS, and submitting the results to `app.bsky.actor.putPreferences` on your new PDS.
40404141-At any point during this process, you can check the status of your new account by calling `com.atproto.server.checkAccountStatus` which will inform you of your repo state, how many records are indexed, how many private state values are stored, how many blobs it is expecting (based on parsing records), and how many blobs have been uploaded.
4141+At any point during this process, you can check the status of your new account by calling `com.atproto.server.checkAccountStatus` which will inform you of your repo state, how many records are indexed, how many private state values are stored, how many blobs it is expecting (based on parsing records), and how many blobs have been uploaded. If you find you are missing blobs and are not sure which, you may use `com.atproto.repo.listMissingBlobs` on your new PDS to find them.
42424343### Updating identity
4444···64646565### After migration
66666767-After migrating, you should be good to start using the app as normal! You'll need to log out and log back in through your new PDS so that the client is talking to the correct service. It's possible that some services (such as feed generators) will have a stale DID cache and may not be able to accurately verify your auth tokens immediately. However, we've found that most services handle this gracefully, and those that don't should sort themselves out pretty quickly.6767+After migrating, you should be good to start using the app as normal! You'll need to log out and log back in through your new PDS so that the client is talking to the correct service. It's possible that some services (such as feed generators) will have a stale DID cache and may not be able to accurately verify your auth tokens immediately. However, we've found that most services handle this gracefully, and those that don't should sort themselves out pretty quickly.
6868+6969+7070+## Example Code
7171+7272+The below code gives an example of how this account migration flow may function. Please note that it is for documentation purposes only and can not be run exactly as is as there is an out-of-band step where you need to get a confirmation token from your email.
7373+7474+It does also not handle some of the more advanced steps such as verifying a full import, looking for missing blobs, adding your own recovery key, or validating the PLC operation itself.
7575+7676+```ts
7777+import AtpAgent from '@atproto/api'
7878+7979+const OLD_PDS_URL = 'https://bsky.social'
8080+const NEW_PDS_URL = 'https://example.com'
8181+const CURRENT_HANDLE = 'to-migrate.bsky.social'
8282+const CURRENT_PASSWORD = 'password'
8383+const NEW_HANDLE = 'migrated.example.com'
8484+const NEW_ACCOUNT_EMAIL = 'migrated@example.com'
8585+const NEW_ACCOUNT_PASSWORD = 'password'
8686+const NEW_PDS_INVITE_CODE = 'example-com-12345-abcde'
8787+8888+const migrateAccount = async () => {
8989+ const oldAgent = new AtpAgent({ service: OLD_PDS_URL })
9090+ const newAgent = new AtpAgent({ service: NEW_PDS_URL })
9191+9292+ await oldAgent.login({
9393+ identifier: CURRENT_HANDLE,
9494+ password: CURRENT_PASSWORD,
9595+ })
9696+9797+ const accountDid = oldAgent.session?.did
9898+ if (!accountDid) {
9999+ throw new Error('Could not get DID for old account')
100100+ }
101101+102102+ // Create account
103103+ // ------------------
104104+105105+ const describeRes = await newAgent.api.com.atproto.server.describeServer()
106106+ const newServerDid = describeRes.data.did
107107+108108+ const serviceJwtRes = await oldAgent.com.atproto.server.getServiceAuth({
109109+ aud: newServerDid,
110110+ })
111111+ const serviceJwt = serviceJwtRes.data.token
112112+113113+ await newAgent.api.com.atproto.server.createAccount(
114114+ {
115115+ handle: NEW_HANDLE,
116116+ email: NEW_ACCOUNT_EMAIL,
117117+ password: NEW_ACCOUNT_PASSWORD,
118118+ did: accountDid,
119119+ inviteCode: NEW_PDS_INVITE_CODE,
120120+ },
121121+ {
122122+ headers: { authorization: `Bearer ${serviceJwt}` },
123123+ encoding: 'application/json',
124124+ },
125125+ )
126126+ await newAgent.login({
127127+ identifier: NEW_HANDLE,
128128+ password: NEW_ACCOUNT_PASSWORD,
129129+ })
130130+131131+ // Migrate Data
132132+ // ------------------
133133+134134+ const repoRes = await oldAgent.com.atproto.sync.getRepo({ did: accountDid })
135135+ await newAgent.com.atproto.repo.importRepo(repoRes.data, {
136136+ encoding: 'application/vnd.ipld.car',
137137+ })
138138+139139+ let blobCursor: string | undefined = undefined
140140+ do {
141141+ const listedBlobs = await oldAgent.com.atproto.sync.listBlobs({
142142+ did: accountDid,
143143+ cursor: blobCursor,
144144+ })
145145+ for (const cid of listedBlobs.data.cids) {
146146+ const blobRes = await oldAgent.com.atproto.sync.getBlob({
147147+ did: accountDid,
148148+ cid,
149149+ })
150150+ await newAgent.com.atproto.repo.uploadBlob(blobRes.data, {
151151+ encoding: blobRes.headers['content-type'],
152152+ })
153153+ }
154154+ blobCursor = listedBlobs.data.cursor
155155+ } while (blobCursor)
156156+157157+ const prefs = await oldAgent.api.app.bsky.actor.getPreferences()
158158+ await newAgent.api.app.bsky.actor.putPreferences(prefs.data)
159159+160160+ // Migrate Identity
161161+ // ------------------
162162+163163+ await oldAgent.com.atproto.identity.requestPlcOperationSignature()
164164+165165+ const getDidCredentials =
166166+ await newAgent.com.atproto.identity.getRecommendedDidCredentials()
167167+168168+ // @NOTE, this token will need to come from the email from the previous step
169169+ const TOKEN = ''
170170+171171+ const plcOp = await oldAgent.com.atproto.identity.signPlcOperation({
172172+ token: TOKEN,
173173+ ...getDidCredentials.data,
174174+ })
175175+176176+ await newAgent.com.atproto.identity.submitPlcOperation({
177177+ operation: plcOp.data.operation,
178178+ })
179179+180180+ // Finalize Migration
181181+ // ------------------
182182+183183+ await newAgent.com.atproto.server.activateAccount()
184184+ await oldAgent.com.atproto.server.deactivateAccount({})
185185+}
186186+```