Skip to content
Pepinby SHIN
Shopify2026-04-257分で読めます
ShopifyFiles APIGraphQL

Shopify Files API の現実 — まるっとフォルダ管理を作って気づいた3つの制約

Shopify Files API の現実 — まるっとフォルダ管理を作って気づいた3つの制約

「Shopifyの Files、なんでフォルダがないんだろう?」と感じたこと、ありませんか。商品画像、ブログのサムネ、PDFカタログ、領収書テンプレ。気づくと数百枚が一覧にダラっと並び、ファイル名で検索する以外に整理する手段がない。わたしは2026年に「まるっとフォルダ管理」を1人で作る過程で、この違和感の正体と、Files API の現実的な制約に正面からぶつかりました。

これは Shopify アプリ「まるっとフォルダ管理」を1人で開発したソロ開発者(SHIN)の体験談です。Shopify Files API の仕様は2026年4月時点の Admin GraphQL(version 2026-04)に基づいています。実装前に必ず最新の公式ドキュメントもご確認ください。

なぜ「まるっとフォルダ管理」を作ったのか

きっかけは、自社で運用している小さな Shopify ストアでした。商品の差し替え画像、季節キャンペーンのバナー、社内向けのPDF、領収書テンプレート。月に数十枚ずつ Files に積まれていって、半年で500枚を越えたあたりで、 管理画面の Files が完全に機能しなくなりました

検索バーはありますが、ファイル名で部分一致するだけ。商品ジャンル別、用途別、年月別、こうしたごく当たり前のフォルダ分けができないのです。Google Drive や Dropbox に慣れた目には、これがかなり辛い。

「2026年のSaaSで、ファイル管理にフォルダがないってどういうこと?」

自社ストア運用中の自分のメモ

調べてみると、Shopify コミュニティでも長年同じ要望が繰り返し出ていました。それなら自分で作ろう、と動き出したのが「まるっとフォルダ管理」です。最初は「メタデータでタグ付ければ済むでしょ」と軽く考えていたのですが、実際に Files API を本気で触り始めて、想像以上に 制約が厚い ことに気づきました。

Shopify Filesでファイルを探すイメージ

やったこと: Files API を本気で触って気づいた3つの制約

ここからが本題です。「まるっとフォルダ管理」を実装する過程で、Files API のドキュメントだけでは見えなかった3つの制約に正面からぶつかりました。順に紹介します。

  1. 2026-04-01

    調査開始

    Files API のスキーマを読み込み、フォルダ実装の方針を検討。最初はメタフィールドで楽勝と思っていました。

  2. 2026-04-10

    プロトタイプで詰まる

    ページネーションと同期処理の壁にぶつかり、設計をやり直し。staged uploads の段差で2日溶かす。

  3. 2026-04-22

    まるっとフォルダ管理 リリース

    最終的に擬似フォルダ + 自DB同期 + Bulk Operation 前提で着地。リリースに到達。

制約1: フォルダ概念がない(メタデータでの擬似実装の現実)

最初に直面したのが、これです。Shopify の File 型には、 フォルダやディレクトリという概念がそもそも存在しませんaltcreatedAtfileStatuspreview などのフィールドはありますが、「親フォルダID」に相当するものはどこにもない。

Shopify Files

Shopify ストアにアップロードされた画像・動画・PDF・3Dモデルなどの汎用ファイルをまとめて扱う仕組み。Admin GraphQL では File インターフェースとして公開され、MediaImageVideoGenericFileModel3d などが実装します。フラットなアセット集合として管理されており、階層構造は持ちません。

ではどうやってフォルダを実現するか。選択肢は実質3つでした。

案A: ファイル名にプレフィックスを付ける

marketing/2026-04/banner.png のように、名前で擬似フォルダを表現する。実装は最も軽いが、リネームが命名規約頼みになり、運用で必ず崩れる。

案B: 自前DBに親子関係を持つ(採用)

Files の id をキーに、こちらのDB側でフォルダ階層と所属関係を持つ。Shopify 標準の Files 画面とは独立した「もう1つのビュー」を提供する形に。

最終的に採用したのは案Bです。理由は、 Shopify 側の File メタフィールドが安定していない から。Files に対するメタフィールドは2024年以降サポートが拡充されていますが、UIから付け外しされたり一括書き換えされる可能性があり、フォルダ構造の正の根拠を Shopify 側に置くのはリスクが高すぎました。

「Shopify 側に状態を持たせれば自分のDB要らないじゃん」と一瞬考えましたが、 Files API のメタフィールド書き込みはレートが厚い うえ、ユーザーが管理画面から消したら整合性が一瞬で崩れます。フォルダのような中核データは、自前DBに正を持たせるほうが安全です。

出典:Shopify Admin GraphQL: File オブジェクト

制約2: GraphQL Files クエリのページネーション仕様

次にぶつかったのが、 files クエリのページネーション です。Admin GraphQL の files(first: N, after: ...) は標準的なカーソルベースですが、 first の上限は250 です。これは Files に限った話ではなく、Shopify の Connection 全般に共通する制約です。

数百枚〜数千枚の Files を扱うストアで、画面表示のたびに first: 250 で何度も叩くと、すぐにスロットリングに到達します。最初に書いた素朴な実装はこんな感じでした。

query InitialNaiveQuery {
  files(first: 250) {
    edges {
      node {
        id
        alt
        createdAt
        ... on MediaImage {
          image {
            url
            width
            height
          }
        }
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}

これを hasNextPage が false になるまで while で回すと、5000枚クラスのストアでは20回連続でクエリが飛びます。Calculated query cost で言うと 1回あたり実コストが軽く500ポイントを超える ことがあり、標準プランの100ポイント/秒では即スロットリング行きでした。

250
files クエリ first の実用上限
100
標準プランの復元レート(pt/秒)
20
5000枚ストアの全件取得に必要なページ数

打ち手は、 「画面表示のたびに API を叩かない」 に尽きました。Files の一覧は Webhook(products/update や Files 系の更新通知)と Bulk Operation で自前DBに同期しておき、ユーザーがフォルダ画面を開いたときは自DBから返す。この方針に切り替えてから、Throttled が一切出なくなりました。

数値は「まるっとフォルダ管理」の開発・検証時のわたし個人比です。ストア規模・画像点数・キャッシュ戦略により変動するため、目安としてご覧ください。

出典:Shopify Admin GraphQL: files クエリ / 出典:Shopify API rate limits

制約3: バルクアップロードの落とし穴(staged uploads の2段構え)

3つ目がいちばん落とし穴でした。 Files にファイルをアップロードする処理 です。「まるっとフォルダ管理」では、ユーザーがローカルから複数ファイルをドラッグ&ドロップで一括投入する機能を作っていました。

最初は fileCreate mutation に直接 URL を渡せばいいと思っていました。実際、外部URL からの登録ならそれで済みます。しかし ローカルファイルから一気に上げたい場合は、stagedUploadsCreate で一時アップロード先を発行 → そのURLにバイナリをPUT → 返ってきた resourceUrlfileCreate に渡す、という2段構えが必須です。これに気づくまでに丸1日溶かしました。

staged uploads

Shopify が提供する、大容量ファイル向けの安全なアップロード手順。stagedUploadsCreate mutation で一時的な署名付きURLとパラメータを受け取り、そのURLに直接バイナリをアップロードしたあと、返却された resourceUrlfileCreate などの後続 mutation に渡す2段階フロー。数MBを超えるファイルや、動画・3Dモデルでは事実上必須となります。

実装の流れはこうなります。

  1. 1

    stagedUploadsCreate を呼び、アップロード先を取得

    input: [{ filename, mimeType, fileSize, resource: IMAGE }] を渡し、stagedTargets に含まれる URL とパラメータを受け取る。

  2. 2

    返ってきた URL にバイナリを直接 POST/PUT

    Shopify ではなく、Google Cloud Storage の署名付きエンドポイントに直接アップロード。ここで CORS や multipart の境界文字列でハマりやすい。

  3. 3

    fileCreate に resourceUrl を渡して登録完了

    originalSourcestagedTargets[i].resourceUrl を入れて mutation。ここで初めて Shopify Files に正式登録される。

  4. 4

    非同期処理の完了を待つ

    fileCreate のレスポンス時点では fileStatus: UPLOADED ではなく PROCESSING のことが多い。後続処理は Webhook かポーリングで READY を待つ。

さらに fileCreate1回のリクエストで最大250件をバッチで投入できる のですが、調子に乗って250件まとめて投げると、事前の stagedUploadsCreate 側でレートに引っかかります。実用上は 1バッチ50件、リクエスト間に小さなディレイ が安定でした。

メリット

数百MBの動画でもタイムアウトせず投入できる / 直接ストレージにアップロードするので Shopify 側のレート消費が小さい / 失敗時のリトライをURL単位で組める

デメリット

2段構えなので途中で失敗するとゴミURLが残る / mimeType と fileSize を事前に正確に渡さないと弾かれる / 完了は非同期、PROCESSING 中の File を即時表示するとプレビューが空になる

出典:Shopify Admin GraphQL: stagedUploadsCreate / 出典:Shopify Admin GraphQL: fileCreate

フォルダで整理したコンテンツのイメージ

学んだこと・気づき

3つの制約と向き合ってみて、設計初期に持っておきたかった視点が3つあります。

「Shopifyに状態を持たせる」と「自分で持つ」の境界線

フォルダのような アプリの中核ドメイン は、自前DBに正を持たせるのが結局いちばん安定します。Shopify 側のメタフィールドは便利ですが、ユーザーが管理画面から触れる以上、 整合性の最終責任を持てるのは自分のDBだけ です。逆に「ファイルそのもの」は Shopify Files に置いたほうが、CDN・配信・ストア連携のメリットを享受できます。

状態の正をどちらに置くかを最初に決めておかないと、後から擬似フォルダの整合性バグで何度も泣くことになります。

一覧系は「叩かない設計」が結局いちばん速い

first: 250 を駆使して頑張るより、Webhook + Bulk Operation で 自前DBに同期して、画面はDBから返す ほうが、レートにもUXにも優しい。 API は読みに行くものではなく、変更通知を受け取って積み増していくもの という発想に変えてから、Shopify アプリの開発がだいぶ楽になりました。

非同期完了の扱いを最初に決める

fileCreate 直後の File は PROCESSING 状態です。「アップロードしたらすぐサムネが出る」UIを最初に作ると、必ずプレビューが空のまま表示される瞬間があります。 fileStatusREADY になるまで待つ or プレースホルダーを出す という方針を、最初の画面設計の時点で決めておくべきでした。

非同期処理の完了UIは、後付けで綺麗にできた試しがない。最初の30分で決めるか、永久に妥協し続けるかの2択。

リリース直前の自分

ソロ開発者が Shopify Files を扱うときの現実的なアドバイス

最後に、これから Files 周りのアプリや機能を1人で作る方に向けて、わたしが先に知っておきたかったチェックリストを置いておきます。

  • フォルダ・タグ・分類などの中核データは自前DBに正を持たせ、Files の id を外部キーとして紐づける
  • files クエリは first: 250 上限を前提に、ページネーションループ + 変更時 Webhook 同期で組む
  • ローカルからのアップロードは stagedUploadsCreate → 直接PUT → fileCreate の3段で組む
  • 1バッチ50件・小ディレイで投入し、完了は fileStatus: READY を待つ前提でUIを設計する
  • レートは「リクエスト数」ではなく「Calculated query cost のポイント」で見積もる
  • 初回同期は Bulk Operation で1ジョブ化し、増分のみ Webhook で追従する

Shopify Files まわりのアプリは、見た目こそシンプルですが、中身はほぼ「ファイル同期ミドルウェア」です。最初の1週間は機能ではなく 同期と整合性の設計 に時間を使うほうが、結果として早く動くものに辿り着けます。

読者へのメッセージ

「フォルダがない」という一見些細な不便から始まった「まるっとフォルダ管理」ですが、実装してみると Shopify Files の設計思想そのものに触れる旅になりました。フラットなアセット集合として軽量に保ちつつ、必要な拡張はアプリ側に委ねる。これは Shopify 全体に通底する哲学で、 「不便」に見える仕様の裏には、たいていプラットフォームとしての一貫性がある ことに気づかされました。

それでも、自社ストアを運用している身としては、フォルダで整理したいものはフォルダで整理したい。このギャップを埋めるためにアプリを作るのが、わたしのようなソロ開発者の役割だと思っています。同じように Shopify Files で困っている方の参考になれば嬉しいです。

「まるっとフォルダ管理」の設計思想や使い方は、プロダクトページにまとめています。実装側ではなく、 使う側の視点で何を解決したかったか を書き残しているので、良ければ覗いてみてください。

→ 「まるっとフォルダ管理」のプロダクトページを見る

この記事はShopify予約アプリ「まるっと予約」の開発元であるPepinが執筆しています。

Share
SHIN

この記事の執筆者

SHIN

Pepin代表、Webエンジニアとして10年以上の経歴を持ち、
Shopifyアプリ・ストア開発 / webサービス開発 / メディア運営などマルチに活動。

Shopify無料体験を始める