---
title: 'Migrate your Shopify store assets to a new store using File API'
date: '2023-02-25'
lastmod: '2024-09-11'
tags: ['shopify', 'assets', 'file-api', 'migration', 'graphql', 'nodejs']
summary: 'Learn how to migrate your assets from an old Shopify store to a new one in a few simple steps. This guide will show you how to retrieve and download your images using GraphQL and Node.js, and upload them to your new store with ease.'
images: ['/static/images/banners/red-and-white-stop-sign__kommumikation__WOn90Iui_08.jpeg']
authors: ['default']
---

If you are a Shopify Developer and need to migrate all of your data from one store to a new one without the need to pay for a migration app,
then I believe this is the article you need to read <Twemoji emoji="smiling-face-with-sunglasses" />.

## Problem

The problem has been stated above. So let's see what data we have in a store and how we can migrate it to another store:

- **Product**: You can download a **CSV** file containing product data and upload it to the new store.

  <Twemoji emoji="warning" /> With **CSV** files, information about Collections will not be
  migrated, so you will need to recreate the Collection data.

- **Theme**: Download the Zip file and upload it to the new store, and the data will remain the same.

- **Blog posts & custom pages**: Use a free app on the Shopify store called [ExIm ‑ Export / Import data](​​https://apps.shopify.com/exim-export-import-pages-blogs-theme-settings) to migrate.

- **Assets**: These are all the files in your store (which can be viewed in **Admin / Settings / Files**), including images, videos, and fonts. These are the assets used for theme settings, blog posts, and pages. (Product images and videos are automatically migrated when the **CSV** file is uploaded!)

  And the problem I faced when migrating this asset stack was:

      - My store had around ~1000 files, and I couldn't upload them manually.
      - After uploading, I had to select the settings of the theme and page to make them work.

If done manually, this would be an exhausting task! <Twemoji emoji="flexed-biceps" />

## Solution

My solution here is to use the [Shopify File API](https://shopify.dev/api/admin/graphql/reference/products-and-collections/file) in conjunction with **Node.js** to download all files and upload them to the new store.

The **File API** is officially supported by Shopify in API version `2021-07` after the recent **Shopify Unite** event (information on [Shopify Developer Changelog](https://shopify.dev/changelog/file-api-is-now-available)).

The following are the steps that combine manual labor and brainpower to solve these assets <Twemoji emoji="face-with-tears-of-joy" /><Twemoji emoji="face-with-tears-of-joy" />

### Get all file URLs with `GraphQL`

<Twemoji emoji="keycap-1" /> Create a **Private App**

Why create a **Private App**? Because we need to authenticate with Shopify to `request` resources in the store.
The simplest way is to use [Basic HTTP authentication](https://shopify.dev/apps/auth/basic-http) with a **Private App**. The steps to create a **Private App** are as follows:

- Go to **Store Admin / Apps**
- Click **Manage private apps** (at the bottom of the page) => **Create new private app**
- Fill in **App name** and **Emergency email** in the **App details** section
- Click **Show inactive Admin API permissions** in the **Admin API** section, scroll to the **Files** section, and select `Read access`.
- Click => **Save** => **Create app**
- That's it, you have created a **Private App** to authenticate with Shopify. <Twemoji emoji="party-popper" />

<Twemoji emoji="keycap-2" /> Get the **Access Token**

The easiest way to send authenticated `requests` to Shopify is to add the request header `X-Shopify-Access-Token: {access_token}`,
where `{access_token}` is the **Admin API password** of the **Private App** we just created.

![private-app-password](/static/images/private-app-password.png)

Copy the `password` in the **Admin API** section of the **Private App** you just created; this is the `access_token` we need for the next step.

<Twemoji emoji="keycap-3" /> Send a **GraphQL** request with **Postman**

Because we need to send multiple requests, it's convenient to use an HTTP Client to configure headers and copy responses (such as Postman, Postwoman, Insomnia, etc.).
Here, I choose **Postman**. (Download link [here](https://www.postman.com/))

After downloading, create a `POST` request like this:

![postman-1](/static/images/postman-1.png)

In which:

- Request URL: <code style={{ wordBreak: 'break-word' }}>https://your-store.myshopify.com/admin/api/unstable/graphql.json</code> with `unstable` as the API version supporting File API.

- <Twemoji emoji="warning" /> note: The `Content-Type` header must use the value `application/json`,
  not `application/graphql` (Shopify has
  [note](https://shopify.dev/apps/auth/basic-http#shopify-access-token) this)

- The value of the `X-Shopify-Access-Token` header is the `access_token` or `password` of the **Private App** you just created.

Next, switch to the **Body** tab, select **GraphQL**

![postman-2](/static/images/postman-2.png)

And add the following `query`

```js showLineNumbers
{
  files(first: 250) {
    edges {
      node {
        ... on MediaImage {
          image {
            originalSrc
          }
        }
      }
      cursor
    }
    pageInfo {
      hasNextPage
      hasPreviousPage
    }
  }
}
```

Each `node` belongs to one of two types: [`MediaImage`](https://shopify.dev/api/admin/graphql/reference/products-and-collections/mediaimage) or [`GenericFile`](https://shopify.dev/api/admin/graphql/reference/products-and-collections/genericfile),
where `GenericFile` only has a few files (mostly **Fonts**), so I'll focus on `MediaImage` only.

<Twemoji emoji="warning" /> Here are some important notes:

- `first: 250`: is the maximum limit of Shopify for one `request`.
- `cursor`: because of the limit, we need to create multiple requests (for example, if my store has ~1000 images, I need 4 requests). The cursor here is the **pagination** for GraphQL (similar to REST, where you use `&page=1`, `&page=2`... to request the next page).
- `pageInfo`: It is added only to know if there is a next page or not. <Twemoji emoji="beaming-face-with-smiling-eyes" />

The result will look like this: <Twemoji emoji="party-popper" />

![postman-3](/static/images/postman-3.png)

To `request` the next page, you need to add a `query` parameter named `after` with the value of the `cursor` of the **last element** in the `edges` array in the **response** (<Twemoji emoji="warning" /> the last element!).

![postman-4](/static/images/postman-4.png)

That's all <Twemoji emoji="grinning-face-with-sweat" />. With 4 requests, I already have 1000 URLs of all the images in the old store!

### Download files with Node.js

Now, we need to download all these files and upload them to the new store.

Do we need to choose the settings for this asset again when uploading to the new store <Twemoji emoji="thinking-face" /> ?

The answer is no! Thanks to a very cool mechanism of Shopify!

> If you upload a file with the same name as in the old store, it will automatically inherit the settings of the theme/page/blog... that were migrated <Twemoji emoji="exploding-head" /><Twemoji emoji="exploding-head" />.

Moreover, the URL of the file obtained with **GraphQL** already contains the file name: <code style={{ wordBreak: 'break-word' }}>https://cdn.shopify.com/s/files/1/0561/2742/2636/files/fav_icon_dark_1.png?v=1618297070</code> The file name of this file is `fav_icon_dark_1.png`.

Now we only need to download the 1000 files, save them with the extracted name from the URL, and upload them to the new store <Twemoji emoji="smiling-face-with-sunglasses" /><Twemoji emoji="smiling-face-with-sunglasses" />.

With just a few lines of `Node.js` code combined with `regex`, we can extract the name and download the file:

```js showLineNumbers
let fs = require('fs')
let fetch = require('node-fetch')
let fileData = require('./data')

async function download(url, filename) {
  let response = await fetch(url)
  let buffer = await response.buffer()
  await fs.writeFile(filename, buffer, () => {})
}

console.time('FILE_DOWNLOAD')
Promise.all(
  fileData.map(async ({ node }, ind) => {
    let src = node?.image?.originalSrc
    if (src) {
      let filename = (src.match(/.*\/files\/(.*)\?v=.*/) || [])[1]
      if (filename) {
        await download(src, `./download/${filename}`)
      } else {
        console.warn(`Couldn't get filename of: ${src} at index ${ind}`)
      }
    } else {
      console.log(`No file at index: ${ind}`)
    }
  })
).then(() => console.timeEnd('FILE_DOWNLOAD'))
```

Note that `fileData` is the array of `edges` from the above requests!

The entire `script` is in this [repository](https://github.com/hta218/shopify-file-downloader/blob/main/index.js).

![file-download](/static/images/file-download.jpg)

It downloads very quickly! <Twemoji emoji="smiling-face-with-sunglasses" />

### Upload to the new store

No need to write any code for this step because doing it manually is the fastest way. <Twemoji emoji="grinning-squinting-face" />

Go to Store **Admin / Settings / Files** => click **Upload files**. Shopify only allows uploading a maximum of 200 files at a time, so uploading a few times will complete the process!

At this point, the migration process is complete <Twemoji emoji="party-popper" /><Twemoji emoji="party-popper" />. Check the results on the new store and good luck!

Happy migrating <Twemoji emoji="clinking-beer-mugs" />.

### Refs

- [https://shopify.dev/apps/auth/basic-http](https://shopify.dev/apps/auth/basic-http)
- [https://www.shopify.com/partners/blog/getting-started-with-graphql](https://www.shopify.com/partners/blog/getting-started-with-graphql)
- [https://help.shopify.com/en/manual/apps/private-apps#enable-private-app-development-from-the-shopify-admin](https://help.shopify.com/en/manual/apps/private-apps#enable-private-app-development-from-the-shopify-admin)
