サイトの移行

このページ

サイトパッケージは、EmDash サイトのコンテンツモデル、コンテンツ、編集履歴、サイトの表示、設定、メディアファイルをまとめたポータブルなコピーです。サイトパッケージをインポートすると、サイトを別の EmDash デプロイメントに移動できます。移動先は、SQLite、PostgreSQL、Cloudflare D1 など、異なるデータベースを使用するデプロイメントでもかまいません。

インポートは、コンテンツ領域が空の新しいサイトに書き込みます。EmDash は何かを書き込む前にパッケージ全体を検査し、再開可能な小さなステップでインポートを実行し、インポートしたサイトを読み戻して、結果がパッケージと一致するとレシートを発行します。

サイトパッケージには、ユーザー、認証情報、シークレットは含まれません。ただし、著者やコメント投稿者のメールアドレスを含め、サイト上のすべてのエントリーとコメントが含まれます。データベースのバックアップと同じように慎重に保管・送付してください。

適切なコピーの種類を選ぶ

仕組み目的インポート可能メディアファイルユーザーとシークレット
シードファイルコンテンツモデルとサンプルコンテンツを初期構築するはい、シードのセマンティクスでいいえいいえ
プレビュースナップショット分離されたプレビューレンダリングにデータを投入するプレビューのみいいえいいえ
JSON バックアップ選択した状態をデータベース形式で確認するいいえいいえいいえ
データベースとメディアの未加工バックアップ1 つのデプロイメントを復旧する同じ種類のデータベースへの復元別途コピーはい
サイトパッケージサイトを別の EmDash サイトに移動するはい、空のサイトへはいいいえ。著者の名前とメールアドレスのみ

データ損失後にデプロイメントを復旧するには、データベースの未加工バックアップを使用します。別の場所にサイトの新しいコピーを作成するには、サイトパッケージを使用します。

サイトパッケージに含まれるもの

サイトパッケージには次のものが含まれます。

  • コレクション、フィールド、すべてのバージョンを含むブロックタイプ、タクソノミー定義、リレーション定義、バイラインフィールド定義
  • すべてのロケールのすべてのコンテンツエントリー(下書き、予約済みエントリー、ゴミ箱内のエントリー、リビジョン履歴、翻訳グループを含む)
  • タクソノミーのタームとタームの割り当て、バイラインとクレジット、コンテンツ参照、SEO レコード
  • メニューとメニュー項目、ウィジェットエリアとウィジェット、セクション、リダイレクト
  • コメントとコメントへのリアクション(エクスポートでコメントを除外した場合を除く)
  • メディアフォルダー、メディアのメタデータ、準備が完了したすべてのメディアファイルのバイト
  • 以下に示すポータブルなサイト設定

パッケージは、JSON フィールドや Portable Text などの JSON 値を、オブジェクトのキーをソートした状態で保存します。そのため、インポートされた値のキーの並び順が元のサイトと異なる場合があります。それ以外の点で値は変わりません。

ポータブルな設定

エクスポートされるのは次の設定のみです: site:title、site:tagline、site:logo、site:favicon、site:postsPerPage、site:dateFormat、site:timezone、site:social、site:seo、emdash:site_title、emdash:site_tagline、emdash:locale。

ターゲットサイトは、自身のサイト URL(site:url と emdash:site_url)、サイト ID、セットアップ状態、バックアップ設定を保持します。インポートでこれらが上書きされることはありません。

インポートプランでは、セットアップウィザードが書き込んだターゲットのタイトルとタグラインを維持するか、パッケージの値を使用するかを尋ねられます。デフォルトではパッケージの値が使用されます。

プリンシパル

ユーザーアカウントがパッケージとともに移動することはありません。コンテンツ、リビジョン、メディア、バイライン、コメントが参照している元サイトのユーザーごとに、パッケージにはプリンシパルが含まれます。プリンシパルは、ユーザーの ID、表示名、メールアドレスで構成されます。プリンシパルには、ロール、パスワード、パスキー、セッション、トークンはありません。

インポート中に、各プリンシパルをターゲットサイトのユーザーにマッピングするか、マッピングしないままにします。著者をターゲットユーザーにマッピングするを参照してください。

コメント

コメントには、投稿者の名前とメールアドレス、本文、ステータス、スレッド構造、タイムスタンプ、モデレーションのメタデータが含まれます。IP アドレスのハッシュとユーザーエージェントはエクスポートされません。

リアクションはその件数を保持します。エクスポーターは各投票者のハッシュを新しいランダム値に置き換えるため、ターゲットではリアクションとそれを行った訪問者を結び付けることはできません。

サイトパッケージに含まれないもの

サイトパッケージには、次のものは一切含まれません。

  • ユーザー、セッション、パスキー、OAuth アカウント、許可されたドメイン、API トークン、OAuth クライアント、認可コード、デバイスコード
  • プラグインのストレージ、プラグインの状態、プラグインの設定(プラグインのシークレットを含む)
  • ポータブルな設定以外の設定(プレビューの署名シークレットなど)
  • 監査ログ、レート制限、編集ロック、スケジュールされたタスクの状態、404 ログ、マイグレーション履歴
  • メディアの使用状況レコードと検索インデックス(インポートで再構築されます)
  • 元サイトのストレージキー、バケット名、データベース名、バインディング名
  • アップロードが完了していないものなど、準備が完了していないメディア

外部メディアプロバイダーのメディアは外部のままです。パッケージは参照を保持しますが、プロバイダーのファイルはコピーされません。

ターゲットサイトを準備する

以下のすべての要件を満たすサイトにインポートしてください。ターゲットのコンテンツ、ロケール、アップロード上限、対応フォーマットがパッケージに適合しない場合、分析でブロッカーが報告されます。

  • 管理者アカウント。 インポートは、サインインした管理者として、または API トークンを使用して実行されます。セットアップ時にターゲットの管理者を作成してください。
  • ストレージバックエンド。 元サイトとターゲットの両方に、ストレージの設定が必要です。EmDash はそこにパッケージファイルを一時的に配置します。
  • コンテンツがないこと。 ターゲットには、エントリー(ゴミ箱内のエントリーを含む)、リビジョン、メディアやメディアフォルダー、バイラインやバイラインフィールド、コメント、リダイレクト、タームの割り当て、リレーション、SEO レコード、管理画面で作成したセクション、セットアップ後に作成したコレクションやブロックタイプが存在してはいけません。公式テンプレートからセットアップしたサイトはこの条件を満たします。セットアップで作成されたものはセットアップスキャフォールドです。つまり、シードで作成されたコレクションとブロックタイプ、タクソノミー定義と未割り当てのターム、メニューとその項目、ウィジェットエリアとそのウィジェット、テーマのセクションです。プランにはスキャフォールドが一覧表示され、プランを確定するとインポートによって削除されます。
  • パッケージが使用するすべてのロケール。 パッケージの各ロケールを、ターゲットの i18n 設定に追加してください。i18n 設定のないサイトは en のみを受け付けます。ロケールは大文字と小文字を区別せずに照合され、インポートでは各ロケールがターゲットで設定された大文字・小文字の表記で書き込まれ、locale_recased として宣言されます。
  • 十分な大きさのアップロード上限。 すべてのメディアファイルが、ターゲットの maxUploadSize(デフォルトは 50 MiB)に収まる必要があります。
  • フォーマットバージョン 1。 ターゲットは、パッケージのフォーマットバージョンと、必要なすべての機能をサポートしている必要があります。

次のリクエストは、サポートされているフォーマットバージョン、機能、制限を返します。その portableDomain オブジェクトは、サイトがインポートを受け入れられるかどうか、受け入れられない場合はその理由を報告します。

curl https://new.example.com/_emdash/api/admin/transfer/capabilities \
  -H "Authorization: Bearer $EMDASH_TOKEN"

サイトをエクスポートする

エクスポートは、上限のあるステップでサイトを読み取り、パッケージをサイトのストレージに書き込みます。エクスポートが完了する前に、エクスポーターはインポートと同じ方法で完成したパッケージを検証します。エクスポート中にサイトへの書き込みが成功すると、エクスポーターは最初からやり直します。エントリーの編集ロックの取得や更新は書き込みとは見なされません。3 回試行しても完了しない場合は TRANSFER_EXPORT_CONCURRENT_WRITES で失敗します。

エクスポートのファイルは、エクスポートの作成から 7 日間利用できます。それを過ぎると、ダウンロードは TRANSFER_EXPIRED を返します。

管理画面でエクスポートする

  1. Settings → Transfer を開きます。このページは管理者が利用できます。

  2. Export セクションで Include comments をオフにすると、コメントとリアクションが除外されます。

  3. Export site を選択します。ページにエクスポートの進行状況が表示されます。ページは開いたままにしてください。ページを離れても、戻ってきたときにエクスポートが続行されます。

  4. Export ready が表示されたら、Download package を選択し、.emdash ファイルの保存先を選びます。ページにはダウンロードされたファイル数とバイト数が表示され、Stop でダウンロードをキャンセルできます。

このセクションには、パッケージのダイジェスト、種類ごとのレコード数、サイトの最近のエクスポートも表示されます。各エクスポートには、有効期限が切れるまで個別のダウンロードボタンがあります。

Download package は、エクスポートを 1 ファイルずつ取得し、各ファイルのサイズと SHA-256 ダイジェストをマニフェストと照合して、ブラウザー内で .emdash ファイルを組み立てます。そのため、Cloudflare Workers 上でも、あらゆるサイズのサイトで動作します。ファイルが一致しない場合、ダウンロードはエラーで停止します。Chrome、Edge、その他の Chromium ベースのブラウザーは、ファイルを直接ディスクに書き込みます。その他のブラウザーはダウンロードが完了するまでパッケージ全体をメモリに保持するため、約 500 MB を超えるエクスポートでは、Chromium ベースのブラウザーまたは CLI の使用が推奨されます。

Download as one file は、代わりにアーカイブを 1 回のレスポンスでサーバーに要求します。小規模なサイトに適しています。Cloudflare Workers では、大規模なサイトが 1 回のリクエストの制限を超える場合があります。

CLI でエクスポートする

元サイトにログインしてから、パッケージファイルにエクスポートします。

npx emdash login --url https://example.com
npx emdash site export --url https://example.com --output site.emdash

このコマンドはエクスポートを完了まで進め、パッケージを 1 ファイルずつダウンロードし、各ファイルのサイズとダイジェストを確認して、site.emdash を書き込みます。コメントとリアクションを除外するには --no-comments を追加します。コマンドが中断された場合は、同じオプションで再実行すると同じエクスポートが再開されます。emdash site export のリファレンスを参照してください。

REST API でエクスポートする

advance を呼び出すたびに 1 ステップが実行され、次の呼び出しまでの待機時間である nextRequestInMs が返されます。nextRequestInMs が null になるとエクスポートは完了です。

これらの例では、transfer:export スコープを持つパーソナルアクセストークンを使用しています。トークンスコープを参照してください。

  1. エクスポートを開始します。コメントとリアクションを除外するには、本文として { "comments": false } を送信します。Idempotency-Key ヘッダーを指定すると、再試行したリクエストが新しいエクスポートを開始せずに同じエクスポートを返します。異なるオプションで同じキーを再利用すると、409 TRANSFER_IDEMPOTENCY_CONFLICT で失敗します。

    curl -X POST https://example.com/_emdash/api/admin/transfer/exports \
      -H "Authorization: Bearer $EMDASH_TOKEN" \
      -H "Idempotency-Key: move-to-new-host"
  2. nextRequestInMs が null になるまでエクスポートを進めます。呼び出しの間には、返されたミリ秒数だけ待機してください。operation.progress は、done と total のステップ数、これまでに書き込まれた records、そしてパッケージサイズが判明した後は bytesDone と bytesTotal を報告します。

    curl -X POST https://example.com/_emdash/api/admin/transfer/exports/$EXPORT_ID/advance \
      -H "Authorization: Bearer $EMDASH_TOKEN"
  3. operation.state が complete であることを確認します。failed のエクスポートでは、理由が operation.errorCode に含まれます。

  4. パッケージを 1 つの .emdash ファイルとしてダウンロードします。

    curl -o site.emdash \
      https://example.com/_emdash/api/admin/transfer/exports/$EXPORT_ID/archive \
      -H "Authorization: Bearer $EMDASH_TOKEN"

.emdash ファイルは、最初のエントリーが manifest.json である非圧縮の tar アーカイブです。アーカイブはすべてのファイルを 1 回のレスポンスでストリーミングします。Cloudflare Workers では、大規模なサイトが 1 回のリクエストの制限を超える場合があります。その場合は、exports/{id}/manifest から manifest.json を、exports/{id}/files/{path} から各ファイルをダウンロードしてください。ダウンロードされる各ファイルは、ストリーミング中に記録済みのダイジェストと照合されます。エクスポート後に保存済みのバイトが変更されていた場合、ダウンロードは完了せずにエラーで終了します。

サイトをインポートする

インポートはパッケージから作成され、分析されてプランになり、そのプランをダイジェストで確定した後にのみ実行されます。実行が開始されていないインポートは、作成から 24 時間で期限切れになります。

管理画面、CLI、REST API のいずれでもすべてのステップを実行できます。AI エージェントは、MCP ツールを通じて、すでにアップロードされたインポートを分析して開始できます。

管理画面でインポートする

  1. ターゲットサイトで Settings → Transfer を開きます。サイトがインポートを受け入れられる場合は Import セクションが表示されます。受け入れられない場合は、インポートを妨げている既存のものが一覧表示されます。

  2. Choose package file を選択し、.emdash ファイルを選びます。ブラウザーがパッケージを検査し、分割してアップロードします。アップロード中にサイト上で変更されるものはありません。アップロードが止まった場合は、同じファイルを再度選択すると、中断したところから続行されます。

  3. アップロードが完了すると、サイトがパッケージを分析します。ページを離れて後で戻ってくることもできます。

  4. インポートを確認します。元サイト、エクスポート日時と EmDash のバージョン、サイズ、パッケージのダイジェスト、種類ごとのレコード数が表示されます。Blockers と Warnings、プランの変換を一覧表示する Differences from the source site、種類別にまとめられた Starter content that will be removed を確認してください。インポートプランを確認するを参照してください。

  5. Authors で、各著者のコンテンツを所有させるこのサイトのユーザーを選択するか、Don’t map を選択します。ユーザーのメールアドレスと一致する著者には Matched by email が表示されます。著者をターゲットユーザーにマッピングするを参照してください。

  6. Site identity で、パッケージのサイトタイトルとタグラインを使用するか、このサイトのものを維持するかを選択します。

  7. Start import を選択して確定します。プランにブロッカーがある間、このボタンは無効です。インポートが完了するまで、サイト上での編集は一時停止されます。

  8. 進行状況を確認します。インポートが完了すると、ページには Verified バッジ付きのレシートと、レシート、パッケージ、プラン、コンテンツの各ダイジェストが表示されます。Copy receipt を選択すると、レシートの JSON のコピーを保存できます。

このページでは、アップロードからインポート完了までの間は Cancel import も利用でき、書き込みを開始したインポートが失敗またはキャンセルされた後は Abandon import も利用できます。いずれも確認が求められます。インポートをキャンセルすると未完了のインポートを破棄するを参照してください。

CLI でインポートする

ターゲットサイトにログインしてから、パッケージを分析します。

npx emdash login --url https://new.example.com
npx emdash site import site.emdash --url https://new.example.com --analyze

このコマンドは、パッケージファイル全体をローカルで検査し、アップロードして分析し、プランをそのプランダイジェストとともに出力します。プランにブロッカーがある場合は、終了コード 2 で終了します。インポートプランを確認するの説明に従ってプランを確認してください。

プランの決定を変更するには、決定フラグを付けて --analyze を再実行します。--map-principal は、ID またはメールアドレスで指定したプリンシパルを、ID またはメールアドレスで指定したターゲットユーザー、または none にマッピングします。--use-target-title と --use-target-tagline は、ターゲットのタイトルとタグラインを維持します。

npx emdash site import site.emdash --url https://new.example.com --analyze \
  --map-principal editor@example.com=editor@example.com \
  --map-principal 01J8ZQ4Y6T2N0D3VJ5R7K9M1PX=none \
  --use-target-title

確認したプランを、そのダイジェストを渡して実行します。

npx emdash site import site.emdash --url https://new.example.com \
  --plan sha256:3f1c… --confirm

このコマンドはインポートを完了まで実行し、レシートを出力します。中断された場合は、emdash site import resume <operation-id> で続行します。emdash site import status <operation-id> はインポートの状態を出力し、emdash site import receipt <operation-id> はレシートを再度出力します。emdash site import のリファレンスを参照してください。

REST API でインポートする

サーバーは .emdash アーカイブではなく、パッケージ内のファイルを扱います。まずアーカイブを展開してください。アーカイブには、manifest.json、index/ 配下のインデックスファイル、records/ 配下のレコードファイル、media/ 配下のメディアファイルが含まれます。マニフェストはすべてのファイルのサイズと SHA-256 ダイジェストを固定するため、パッケージのダイジェストによってパッケージ全体が識別されます。

これらの例では、transfer:analyze と transfer:execute のスコープを持つトークンを使用しています。

  1. インポートを作成します。manifest.json のバイトを変更せずにリクエスト本文として送信します。レスポンスには、オペレーションと、サーバーがまだ必要としているファイルの最初のページが含まれます。

    curl -X POST https://new.example.com/_emdash/api/admin/transfer/imports \
      -H "Authorization: Bearer $EMDASH_TOKEN" \
      -H "Idempotency-Key: move-to-new-host" \
      --data-binary @site/manifest.json
  2. 不足しているファイルをそれぞれ imports/{id}/files/{path} にアップロードします。Content-Length ヘッダーはファイルの宣言済みサイズと等しく、バイトは宣言済みのダイジェストと一致する必要があります。

    curl -X PUT \
      https://new.example.com/_emdash/api/admin/transfer/imports/$IMPORT_ID/files/index/000000.ndjson \
      -H "Authorization: Bearer $EMDASH_TOKEN" \
      --data-binary @site/index/000000.ndjson

    インデックスファイルをアップロードすると、そのファイルに列挙されているレコードファイルとメディアファイルが宣言されます。アップロードのバッチごとに imports/{id}/missing を再度リクエストし、項目が返されなくなるまで続けてください。

    すでに保存されているファイルをアップロードすると、再度検査されます。保存されているコピーが一致しなくなっている場合は、アップロードによって置き換えられ、レスポンスで alreadyVerified: false が報告されます。

  3. パッケージを分析します。nextRequestInMs が null になるまで imports/{id}/analyze を呼び出します。最後のレスポンスには、plan とその planDigest が含まれます。

    curl -X POST https://new.example.com/_emdash/api/admin/transfer/imports/$IMPORT_ID/analyze \
      -H "Authorization: Bearer $EMDASH_TOKEN"
  4. プランを確認し、すべてのブロッカー、警告、変換を読みます。インポートプランを確認するを参照してください。

  5. デフォルトが望むものでない場合は、決定を送信します。送信するたびに、新しいプランとプランダイジェストが返されます。実行がリクエストされるとプランは固定され、決定の送信は 409 TRANSFER_INVALID_STATE で失敗します。

    curl -X POST https://new.example.com/_emdash/api/admin/transfer/imports/$IMPORT_ID/analyze \
      -H "Authorization: Bearer $EMDASH_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{ "decisions": { "principalMappings": { "01J8ZQ4Y6T2N0D3VJ5R7K9M1PX": null }, "siteTitle": "target" } }'
  6. 確認したダイジェストを指定してインポートを開始します。

    curl -X POST https://new.example.com/_emdash/api/admin/transfer/imports/$IMPORT_ID/execute \
      -H "Authorization: Bearer $EMDASH_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{ "packageDigest": "sha256:…", "planDigest": "sha256:…" }'
  7. 呼び出しの間に返された待機時間だけ待ちながら、nextRequestInMs が null になるまでインポートを進めます。

    curl -X POST https://new.example.com/_emdash/api/admin/transfer/imports/$IMPORT_ID/advance \
      -H "Authorization: Bearer $EMDASH_TOKEN"
  8. operation.state が complete であることを確認してから、imports/{id}/receipt からレシートを読み取ります。

いずれかのダイジェストが、一時配置されたパッケージまたは現在のプランと異なる場合、実行は TRANSFER_PACKAGE_DIGEST_MISMATCH または TRANSFER_PLAN_DIGEST_MISMATCH で失敗します。imports/{id}/plan から現在のプランを読み取り、再度確認して、そのダイジェストで再試行してください。

著者をターゲットユーザーにマッピングする

分析では、各プリンシパルが、表示名、メールアドレス、それを参照しているパッケージレコードの数とともに一覧表示されます。大文字と小文字を区別せずに比較して同じメールアドレスを持つターゲットユーザーがちょうど 1 人いる場合、プランはそのユーザーを提案し、デフォルトでプリンシパルをそのユーザーにマッピングします。提案のないプリンシパルは、マッピングなしの状態から始まります。

マッピングを変更するには、emdash site import --analyze に --map-principal を渡すか、分析エンドポイントに principalMappings を送信します。各マッピングは、ターゲットユーザーを指定するか、プリンシパルをマッピングなしのままにします(CLI では none、API では null)。EmDash は、各マッピングをエントリーの著者、リビジョンの著者、メディアのアップロード者、バイラインのユーザーリンク、コメントの投稿者に適用します。

マッピングされていないプリンシパルへの参照は削除されます。マッピングされていない著者がアカウントにリンクされたバイラインも持っていた場合、インポーターは、明示的なバイラインクレジットがなく、そのロケールに著者のバイラインがある著者の各エントリーに、そのバイラインを明示的にクレジットします。そのため、著者のクレジットはページ上に残ります。

次の 2 種類のマッピングは、principal_conflict ブロッカーを生じさせます。

  • 同じロケールに複数のバイラインを持つプリンシパルが、1 人のユーザーにマッピングされている
  • 同じロケールにバイラインを持つ 2 つのプリンシパルが、同じユーザーにマッピングされている

ターゲットユーザーは、ロケールごとに 1 つのバイラインしか持てません。いずれかのプリンシパルをマッピングなしにするか、プリンシパルを別々のユーザーにマッピングしてください。

インポートプランを確認する

プランには、インポートで作成されるもの、適用される決定、そして 3 種類の検出結果が一覧表示されます。

  • ブロッカーは実行を妨げます。プランにブロッカーがなくなるまで、実行は TRANSFER_PLAN_BLOCKED を返します。principal_conflict を解決するには、プリンシパルのマッピングを変更します。それ以外のブロッカーには、パッケージまたはターゲットの変更が必要です。インポートをキャンセルし、変更を行ってから、新しいインポートを作成してください。
  • 警告は、インポートを停止させないパッケージ内の問題を示します。警告はレシートにコピーされます。
  • 変換は、元サイトとインポートされたサイトとの間の、正確で宣言された差分です。エクスポーターによる変更が先に、次にインポートによる変更が一覧表示されます。検証では、インポートされたサイトをパッケージと比較する際に、インポートの変換が適用されます。

プランに一覧表示されるブロッカーと警告は、最大 500 件です。issues_truncated 警告は、それ以外に何件見つかったかを報告します。

ブロッカー

コード意味
package_invalidパッケージのファイルまたはパスが検証に失敗しました。
unsupported_formatターゲットがパッケージのフォーマットまたはフォーマットバージョンをサポートしていません。
unsupported_featureパッケージが、ターゲットでサポートされていない機能を必要としています。
limit_exceededパッケージのファイルまたはレコードが制限を超えています。
file_missing宣言されたパッケージファイルがアップロードされていません。
file_mismatchパッケージファイルのサイズまたはダイジェストが宣言と一致しません。
record_invalidレコードの形式が不正であるか、正規化された JSON ではありません。
record_count_mismatchある種類のレコード数がマニフェストと異なります。
record_order_invalidレコードの順序が正しくないか、親が子より後に現れています。
duplicate_id同じ種類の 2 つのレコードが同じ ID を共有しています。
dangling_referenceレコードが、パッケージに含まれていないレコードを参照しています。これには、パッケージにないブロックタイプを指定したブロックフィールドや、現在のバージョンがパッケージにないブロックタイプも含まれます。
reference_cycleターム、コメント、またはメニュー項目が自分自身を親にしています。
media_ref_invalidコンテンツが、パッケージに含まれていないメディアレコードを参照しています。
media_blob_missingメディアレコードのファイルがパッケージに含まれていません。
media_blob_too_largeメディアファイルがターゲットの maxUploadSize より大きいです。
target_not_emptyターゲットにすでにコンテンツがあります。ブロッカーの detail に、見つかったものが示されます。
locale_not_configuredパッケージが、ターゲットの i18n 設定に含まれていないロケールを使用しています。
field_type_unknownフィールドまたはバイラインフィールドが、ターゲットでサポートされていないタイプを使用しています。
principal_conflictプリンシパルのマッピングにより、1 人のユーザーが同じロケールに 2 つのバイラインを持つことになります。
integer_out_of_range整数がターゲットデータベースの整数の範囲外です。PostgreSQL は整数を 32 ビットで保存します。
value_constraint_violation管理 API が拒否する値です。以下のリストを参照してください。
unique_violationレコードが、ターゲット上の別のレコードの一意キーと重複することになります。

インポーターはレコードを直接書き込むため、分析では、管理 API がそれらのレコードを保存するときに適用するのと同じチェックが適用されます。次の値はいずれも value_constraint_violation です。

  • フィールドのカラムに収まらないエントリーの値、値のない必須フィールド、またはコレクションに存在しないフィールドの値
  • ソースまたは宛先がサイト上のパスではないリダイレクト、タイプがサポートされていないリダイレクト、ソースパターンが無効なリダイレクト、またはソースがキャプチャしないパラメーターを宛先で使用しているリダイレクト
  • http または https の URL ではないバイラインのウェブサイト、フィールドのタイプや選択肢に合わないバイラインフィールドの値、またはサイトがサポートする数を超える選択肢を持つバイラインフィールド
  • 無効なコレクションの URL パターン
  • 予約済みのスラッグを持つブロックタイプ、空または 200 文字を超えるラベルを持つブロックタイプ、またはブロックタイプエディターが拒否するフィールド定義を持つブロックタイプ
  • http または https の URL でもサイトのパスでもない SEO の正規 URL
  • メニューで許可されていないスキームを持つメニュー項目の URL

警告

コード意味
media_provider_externalコンテンツが外部プロバイダーのメディアを使用しています。参照は保持されますが、ファイルはコピーされません。
media_row_missing設定が、パッケージに含まれていないメディアを参照しています。
soft_reference_dangling任意の参照が、パッケージ内のレコードに解決されません。
redirect_loops_uncheckedパッケージのリダイレクトが多すぎるため、インポート前にループを確認できません。ループを閉じることになるリダイレクトは、無効化された状態でインポートされます。
issues_truncatedプランに一覧表示されている数より多くのブロッカーまたは警告が見つかりました。

変換

エクスポーターは、元サイトのデータに対して行った変更を宣言します。これらの変換にはそれぞれ、レコードの種類と件数が含まれます。

コード意味
orphan_dropped削除されたエントリーのリビジョンなど、親が元サイトにもう存在しないレコードが除外されました。
soft_orphan_dropped削除されたタームへのタームの割り当てや、削除されたエントリーを指すメニュー項目など、存在しないレコードへのリンクが除外されました。
orphan_reference_nulledメディアファイルの削除されたフォルダーなど、存在しないレコードへの参照が削除されました。
avatar_nulledバイラインのアバターまたはセクションのプレビュー画像が、パッケージに含まれていないメディアを参照していたため、削除されました。
media_not_ready_droppedアップロードが完了していないものなど、準備が完了していないメディアが除外されました。
media_ref_unlinkedパッケージに含まれていないメディアへの参照が、コンテンツから削除されました。
media_url_relativized元サイト自身のメディアファイルへの絶対 URL が、ターゲットで解決されるサイト相対 URL に変換されました。
redirect_duplicate_dropped同じソースパスに対する重複したリダイレクトが除外されました。ソースパスごとに 1 つのリダイレクトが保持されました。
unknown_storage_key元サイトに存在しないメディアファイルを、まだ参照しているレコードがあります。それらは変更されずにエクスポートされました。

インポートは、自身による変更を宣言します。

コード意味
principal_mappedプリンシパルへの参照が、マッピング先のターゲットユーザーに書き換えられます。
principal_unmappedマッピングされていないプリンシパルへの参照が削除されます。
seeded_scaffold_removedインポートが書き込む前に、ターゲット上のセットアップスキャフォールドが削除されます。プランには各項目が一覧表示されます。
redirect_loop_disabledループを形成するリダイレクトは、無効化された状態でインポートされます。
search_unsupportedターゲットが PostgreSQL を使用しているため、一覧表示されたコレクションでは検索が無効になります。
float4_rounded小数値が、ターゲットの PostgreSQL の real カラムの精度に丸められます。
locale_recasedロケールが、pt-br を pt-BR とするなど、ターゲットで設定された大文字・小文字の表記で書き込まれます。

インポートを実行する

実行は、次のステージを順に進めます。

  1. ターゲットを予約し、空であることを再確認する。
  2. プランに一覧表示されたセットアップスキャフォールドを削除する。
  3. ブロックタイプ、コレクション、フィールド、タクソノミー定義、リレーション定義、バイラインフィールドを作成する。
  4. メディアファイルをターゲットのストレージにコピーし、メディアレコードを作成する。
  5. タームとバイラインを書き込む。
  6. リビジョンとエントリーを書き込む。
  7. タームの割り当て、バイラインのクレジット、コンテンツ参照、SEO レコードを書き込む。
  8. メニュー、ウィジェット、セクション、リダイレクト、コメント、リアクション、設定を書き込む。
  9. 検索インデックスとキャッシュを再構築し、メディア使用状況の再インデックスをキューに入れる。
  10. 結果を検証する。

advance の各呼び出しは、D1 上の Cloudflare Workers のリクエスト制限に収まる、上限のある 1 ステップを実行します。進行状況はサーバーに保存されます。中断されたリクエストで失われるのは最大でも進行中のステップのみで、すべての書き込みは冪等であるため、ステップを再実行してもレコードは重複しません。

別のリクエストがステップを実行している間、またはステップ中に別のリクエストがオペレーションを引き継いだ場合、advance は短い nextRequestInMs とともにオペレーションを返します。ストレージまたはデータベースのエラーは再試行されます。オペレーションはエラーを記録し、連続して失敗するたびに nextRequestInMs が長くなります。進展のないまま失敗が繰り返されると、インポートは失敗します。

インポート中は書き込みがブロックされる

最初の実行ステップからインポートが完了するまで、EmDash は API への書き込みリクエストを 503 TRANSFER_IMPORT_IN_PROGRESS で拒否します。これには、管理画面、REST API、プラグインのルート、公開コメントの投稿、予約公開、プラグインによるコンテンツの書き込みが含まれます。サインイン、ユーザーと API トークンの管理、エントリーの編集ロック、移行 API 自体は引き続き利用できます。読み取りリクエストはブロックされません。

プラグインの MCP ツールを含む MCP の書き込みツールは、通常のツールエラーの中で TRANSFER_IMPORT_IN_PROGRESS を返して失敗します。読み取り専用の MCP ツールと site_* 移行ツールは引き続き動作するため、MCP 経由で開始したインポートは、MCP 経由で再開、確認、完了できます。

中断後に再開する

管理画面のページは、開いている間だけインポートを進めます。再開するには、Settings → Transfer を再度開くか、emdash site import resume <operation-id> を実行するか、同じオペレーションに対して advance を再度呼び出します。サーバーは最後に完了したステップから続行します。中断されたリクエストがまだオペレーションを保持していた場合、次の呼び出しはその保持が期限切れになるまで、最大 5 分間待機します。

失敗またはキャンセルされたインポートは再開できません。

インポートをキャンセルする

Settings → Transfer で Cancel import を選択するか、emdash site import cancel <operation-id> を実行するか、POST imports/{id}/cancel を送信します。進行中のステップは、現在のバッチの後に停止します。キャンセルしても、すでに書き込まれたレコードは削除されません。

未完了のインポートを破棄する

書き込みを開始した後に失敗またはキャンセルされたインポートは、未完了のサイトが誤って編集されないよう、引き続き書き込みをブロックします。ブロックを解除するには、Settings → Transfer で Abandon import を選択するか、emdash site import abandon <operation-id> を実行するか、POST imports/{id}/abandon を送信します。破棄しても、インポートされたデータは保持されます。

破棄した後、サイトはもう空ではないため、別のインポートを受け入れることはできません。代わりに、新しくセットアップしたサイトにインポートしてください。

書き込みを開始する前に失敗またはキャンセルされたインポートは、書き込みをブロックしないため、破棄する必要はありません。

結果を検証する

検証では、エクスポーターが使用するのと同じコードでインポートされた各レコードを読み戻し、プランで宣言された変換をパッケージのレコードに適用して、両者を比較します。また、種類ごとのレコード数を確認し、インポートされた各メディアファイルを再ダウンロードしてダイジェストを確認します。差分が 1 つでもあると、インポートは TRANSFER_VERIFICATION_FAILED で失敗します。オペレーションの errorDetail には、差分が最大 50 件まで一覧表示されます。

インポートが成功すると、レシートが生成されます。

{
	"operationId": "01J8ZR2C4S6D8F0G2H4J6K8M0N",
	"packageDigest": "sha256:…",
	"planDigest": "sha256:…",
	"targetSiteId": "01J8ZR0A2B4C6D8E0F2G4H6J8K",
	"originSiteId": "01J1A3C5E7G9J1L3N5Q7S9U1W3",
	"formatVersion": "1",
	"importerEmDashVersion": "0.38.0",
	"completedAt": "2026-09-23T10:15:00.000Z",
	"logicalDigest": "sha256:…",
	"counts": { "entry": 412, "media": 96 },
	"warnings": [],
	"verification": "verified",
	"receiptDigest": "sha256:…"
}

レシートは、検証が完了した時点で、targetSiteId で識別されるターゲットサイトが、planDigest で識別されるプランの適用後に、packageDigest で識別されるパッケージの内容を正確に保持していたことを記録します。logicalDigest は、検証済みのレコードを要約したものです。

receiptDigest は、receiptDigest プロパティを除いたレシートの正規化 JSON の SHA-256 ダイジェストです。これにより、発行後に変更されたレシートを検出できます。レシートは署名されていないため、どのサーバーが発行したかを証明するものではありません。それが重要な場合は、認証された接続を介してターゲットからレシートを取得してください。

レシートは、検証が完了した時点のサイトを記述するものです。その後の編集については何も示しません。

データベース間で移行する

パッケージは元サイトのデータベースに依存しません。SQLite、PostgreSQL、D1 のいずれからでもエクスポートでき、そのいずれにもインポートできます。ターゲットが PostgreSQL を使用する場合は、次の違いを考慮してください。

  • PostgreSQL は整数を 32 ビットで保存します。その範囲外の整数は integer_out_of_range ブロッカーになります。
  • PostgreSQL は、number フィールドとメディアのフォーカルポイントを 32 ビット浮動小数点値として保存します。変化する値は float4_rounded として宣言され、検証では丸められた値が比較されます。
  • 全文検索は SQLite と D1 でのみ利用できます。検索が有効なコレクションは、検索を無効にした状態でインポートされ、search_unsupported として宣言されます。

インポーターは、メディアを新しいストレージキーでターゲットのストレージバックエンドに書き込み、それに合わせてコンテンツ、設定、SEO レコード内のメディア参照を書き換えます。元サイトに存在しないメディアファイルへの参照は、変更されずにエクスポートされ、unknown_storage_key として宣言されます。

セキュリティ

  • パッケージは機密情報として扱ってください。 パッケージには、下書きやゴミ箱を含むすべてのコンテンツと、著者やコメント投稿者のメールアドレスが含まれます。公開バケットや共有フォルダーには置かず、不要になったコピーは削除してください。
  • パッケージは信頼できない入力として扱ってください。 インポートでは、書き込む前にパス、サイズ、ダイジェスト、レコードのスキーマ、参照、制限が検査されます。パッケージ内のコードや SQL を実行することも、パッケージ内の URL を取得することもありません。
  • 移行へのアクセス権は慎重に付与してください。 移行には管理者ロールが必要です。admin スコープを持つトークンはすべての移行アクションを実行できるため、エージェントのトークンには必要な移行スコープのみを付与してください。
  • 監査ログを確認してください。 EmDash は、移行アクションをサイトの監査ログに記録します: transfer_export_create、transfer_import_create、transfer_import_execute、transfer_import_cancel、transfer_import_abandon、transfer_import_complete、transfer_import_fail、transfer_approval_approve、transfer_approval_deny。各エントリーには、操作したユーザーと、オペレーションまたは承認(リソースタイプ transfer_operation または transfer_approval)が記録されます。その詳細には ID、ダイジェスト、レコード数、エラーコードのみが含まれ、パッケージの内容は含まれません。同様に、移行エラーの詳細にもパッケージの内容は含まれません。
  • 一時配置領域は非公開にしてください。 EmDash は、パッケージファイルをストレージバケットの transfers/ プレフィックス配下に一時配置し、そのプレフィックスをメディアルート経由で配信することを拒否します。バケットに公開ドメインがある場合は、バックアップと同様に、その範囲をメディアに限定してください。一時配置されたファイルは、オペレーションが終了または期限切れになると削除されます。

トークンスコープ

移行では、3 つの API トークンスコープを使用します。

スコープ許可される操作
transfer:exportエクスポートの開始、進行、ダウンロード。
transfer:analyzeインポートの作成、パッケージファイルのアップロード、分析、プランの読み取り。
transfer:executeインポートの開始、進行、キャンセル、破棄。

admin スコープにはこの 3 つすべてが含まれるため、emdash login が保存するトークンですべての移行を実行できます。各移行スコープはそれぞれ自身のアクションのみを許可し、発行できるのは管理者だけです。これらを使用して、トークンに admin より狭いアクセス権を与えることができます。たとえば、パッケージの分析はできても、エクスポートやインポートはできないエージェントなどです。スコープのリファレンスを参照してください。

エージェントの承認

AI エージェントは、site_* MCP ツールを通じて移行を操作します。これらのツールは、オペレーションを開始し、進行させ、状況を報告します。パッケージのバイトは一切扱わないため、エージェントのユーザーは CLI または REST API を使ってエクスポートをダウンロードし、パッケージをアップロードします。すべてのツールに Admin ロールが必要です。

トークンに admin も対応する移行スコープもない MCP クライアント(たとえば transfer:analyze のみを付与されたエージェント)は、単独でエクスポートやインポートを開始できません。その site_export_start または site_import_start の呼び出しは、保留中の承認リクエストを作成し、TRANSFER_APPROVAL_REQUIRED と承認 ID を返して失敗します。管理者は、Settings → Transfer の Approval requests でリクエストを承認または拒否します。そこには、保留中の各リクエストが、リクエスト者、アクション、有効期限とともに一覧表示されます。セッション専用のエンドポイント POST /_emdash/api/admin/transfer/approvals/{id}/approve と …/deny でも同じことができます。API トークンではリクエストを承認できません。その後、クライアントは承認 ID を付けて呼び出しを繰り返します。承認はこれらの MCP ツールにのみ適用され、REST API には承認パラメーターはありません。

承認は、それをリクエストしたユーザーに、同じトークンから同じ引数で 1 回の呼び出しを許可します。エクスポートの承認はエクスポートオプションに紐付けられます。インポートの承認はオペレーションと両方のダイジェストに紐付けられるため、プランが変更された場合は新しい承認が必要です。保留中のリクエストは 15 分後に、承認済みのリクエストは承認から 15 分後に期限切れになります。オペレーションを開始する再試行で承認は消費されます。オペレーションの開始に失敗した場合は、期限切れになるまで同じ承認で再試行できます。その後、同じユーザーとトークンであれば、スコープなしでその 1 つのオペレーションを確認し、進めることができます。

エージェントのトークンに transfer:export、transfer:execute、admin を付与するのは、人が 1 件ずつ承認することなくエージェントが移行を実行する必要がある場合だけにしてください。

制限

制限値
manifest.json8 MiB
1 レコード1,900,000 バイト
1 つのレコードファイルまたはインデックスファイル4 MiB および 1,000 レコード
パッケージあたりのレコード数5,000,000
パッケージあたりのファイル数1,000,000
JSON のネストの深さ64
1 つのメディアファイルターゲットの maxUploadSize(デフォルトは 50 MiB)

capabilities エンドポイントは、サイトが適用している値を報告します。

ホスティングプロバイダー向け

ホスティングのコントロールプレーンは、REST API だけで顧客のサイトを本番環境に移行できます。

  1. ストレージ、ロケール、maxUploadSize を備えた新しい EmDash サイトをプロビジョニングし、セットアップを完了します。capabilities が portableDomain.empty を true と報告していることを確認します。

  2. transfer:analyze と transfer:execute を持つトークンをコントロールプレーン用に発行します。このトークンは、エージェントやサイト構築ツールには渡さないでください。

  3. インポートを実行し、実行する前にプランの警告に対して独自のポリシーを適用します。ブロッカーのあるプランはすべて拒否してください。

  4. サイトを昇格させる前に、レシートを取得して次の点を確認します。

    • verification が verified である
    • packageDigest が、公開しようとしたパッケージのダイジェストである
    • planDigest が、承認したプランである
    • targetSiteId が、これから昇格させるサイトである
    • receiptDigest が、レシートの正規化 JSON と一致する
  5. ドメインをサイトにルーティングするなどして、サイトを昇格させます。

ステップ 4 が成功するまで、ターゲットにはアクセスできないようにしておいてください。EmDash は、一部だけインポートされたサイトを訪問者から隠しません。

トラブルシューティング

移行エラーには安定したコードが使用されます。各コードとともに HTTP ステータスが示されます。

コードステータス対処方法
TRANSFER_TARGET_NOT_EMPTY409ターゲットにすでにコンテンツがあります。新しくセットアップしたサイトにインポートしてください。Settings → Transfer と capabilities に、サイトが対象外となる理由が一覧表示されます。
TRANSFER_IMPORT_IN_PROGRESS503このサイトでインポートが実行中であるか、未完了のインポートがまだ書き込みをブロックしています。完了を待つか、失敗またはキャンセルされたインポートを破棄してください。
TRANSFER_FENCE_CHECK_FAILED503EmDash は、インポートが実行中かどうかを確認できませんでした。書き込みを再試行してください。
TRANSFER_EXPORT_CONCURRENT_WRITES409エクスポートの実行中にサイトが変更され続けました。編集が落ち着いたときに再度エクスポートしてください。
TRANSFER_EXPIRED410エクスポートのファイルが 7 日後に削除されたか、インポートが 24 時間以内に実行されませんでした。最初からやり直してください。
TRANSFER_FILE_MISSING422宣言されたファイルの一部がアップロードされていません。imports/{id}/missing が一覧表示するものをすべてアップロードしてください。
TRANSFER_FILE_NOT_DECLARED422アップロードパスがパッケージに含まれていません。一覧表示されたパスのみをアップロードしてください。
TRANSFER_FILE_SIZE_MISMATCH422Content-Length またはアップロードされたバイトが、宣言されたサイズと異なります。ファイルを変更せずにアップロードしてください。
TRANSFER_FILE_DIGEST_MISMATCH422アップロードされたバイトが宣言されたダイジェストと異なるか、エクスポート後にエクスポートファイルが変更されました。元のファイルをアップロードするか、再度エクスポートしてください。
TRANSFER_LIMIT_EXCEEDED413ファイルが制限を超えています。メディアの場合は、ターゲットの maxUploadSize を引き上げてください。
TRANSFER_MANIFEST_INVALID422リクエスト本文が有効なマニフェストではありません。manifest.json をバイト単位でそのまま送信してください。
TRANSFER_UNSUPPORTED_FORMAT422ターゲットの EmDash をアップグレードしてください。
TRANSFER_UNSUPPORTED_FEATURE422ターゲットの EmDash をアップグレードしてください。
TRANSFER_CONTAINER_INVALID422.emdash ファイルが有効なパッケージアーカイブではありません。再度ダウンロードしてください。
TRANSFER_PLAN_BLOCKED409プランにブロッカーがあります。インポートプランを確認するを参照してください。
TRANSFER_PACKAGE_DIGEST_MISMATCH409ダイジェストが、一時配置されたパッケージと一致しません。オペレーションの packageDigest を使用してください。
TRANSFER_PLAN_DIGEST_MISMATCH409確認した後にプランが変更されました。現在のプランを読み取り、再度確認してください。
TRANSFER_DECISIONS_INVALID422決定で、不明なプリンシパルまたは存在しないターゲットユーザーが指定されています。マッピングを修正してください。
TRANSFER_INVALID_STATE409オペレーションが、そのリクエストを許可する状態にありません。オペレーションを読み取り、その state に従ってください。
TRANSFER_LEASE_ACTIVE409別のリクエストがステップを実行しています。待ってから再試行してください。
TRANSFER_IDEMPOTENCY_CONFLICT409その Idempotency-Key は、別のオプションを指定したエクスポート、または別のパッケージのインポートですでに使用されています。新しいキーを使用してください。
TRANSFER_RUNTIME_MISMATCH409互換性のない EmDash のバージョンがオペレーションを開始しました。開始したバージョンで完了させるか、新しいオペレーションを開始してください。
TRANSFER_VERIFICATION_FAILED422インポートされたサイトがパッケージと一致しません。errorDetail で差分を確認し、インポートを破棄して、新しいサイトにインポートしてください。
TRANSFER_APPROVAL_REQUIRED403管理者がリクエストを承認する必要があります。エージェントの承認を参照してください。
TRANSFER_APPROVAL_INVALID403承認が不明、拒否済み、期限切れ、使用済み、または別のパラメーターに紐付いています。新しい承認をリクエストしてください。
TRANSFER_SCHEMA_UNCLASSIFIED500データベースに、エクスポーターが認識できないテーブルまたはカラムがあります。データベースのマイグレーションに対応する EmDash のバージョンを実行してください。
INSUFFICIENT_SCOPE403トークンに admin も、リクエストに必要な移行スコープもありません。そのスコープを持つトークンを発行してください。