diff --git a/apps/docs/content/troubleshooting/restore-project-after-90-days-pause.mdx b/apps/docs/content/troubleshooting/restore-project-after-90-days-pause.mdx
new file mode 100644
index 00000000000..69ff401808e
--- /dev/null
+++ b/apps/docs/content/troubleshooting/restore-project-after-90-days-pause.mdx
@@ -0,0 +1,292 @@
+---
+title = "How To Restore Project After 90-Day Pause"
+topics = [ "platform" ]
+keywords = [ "pause", "restore", "90 days", "project", "backup", "migration", "storage" ]
+---
+
+Projects paused for more than 90 days can no longer be restored through Supabase Studio. You can still recover your data by downloading the available backups and migrating them to a new project.
+
+Both the database backup and Storage objects can be downloaded from the [Project Overview](/dashboard/project/_) section in Supabase Studio before the project is deleted.
+
+
+
+Check this [short video walk-through](https://youtu.be/ktsgm8HxWqo?si=5_8IVzL1rp8MWv7m) of the full migration process.
+
+## Step 1: Download your backups
+
+In the [Project Overview](/dashboard/project/_) of your paused project, download:
+
+- **Database backup** — the `.backup` file from the Backups section
+- **Storage objects** — all files from your Storage buckets
+
+
+
+Once a project is deleted, all associated data including backups is permanently removed and cannot be recovered.
+
+
+
+## Step 2: Create a new Supabase project
+
+Create a new project at [database.new](https://database.new) and configure it to match your previous setup (extensions, webhooks, Realtime publications, etc.).
+
+## Step 3: Restore the database
+
+Get the **Session pooler** connection string for your new project from the [Connect dialog](/dashboard/project/_?showConnect=true). Replace the `[YOUR-PASSWORD]` placeholder in the connection string with your database password. If you don't remember it, reset it in [Database Settings](/dashboard/project/_/settings/database).
+
+Unzip the downloaded backup file if it has a `.gz` extension, then run:
+
+```bash
+psql -d [CONNECTION_STRING] -f /path/to/backup_file.backup
+```
+
+Some errors like `object already exists` are expected and can be safely ignored — they occur because the new project already has the default Supabase schemas applied.
+
+See the [Restore Dashboard backup guide](/guides/platform/migrating-within-supabase/dashboard-restore) for detailed instructions and troubleshooting.
+
+## Step 4: Restore Storage objects
+
+Use the [Supabase CLI](/docs/reference/cli/introduction) to copy your downloaded storage files to the new project's buckets:
+
+```bash
+supabase login
+supabase link --project-ref [NEW_PROJECT_REF]
+supabase storage cp /path/to/downloaded/files ss:///bucket_name -r --experimental
+```
+
+Repeat for each bucket. See the [`supabase storage cp`](/docs/reference/cli/supabase-storage-cp) reference for all available flags.
+
+## Step 5: Copy project configurations
+
+Use the [Management API](https://api.supabase.com/api/v1) to copy configurations (Auth, Realtime, Storage, etc.) from the paused project to the new one. You need Owner or Admin permissions on both projects.
+
+Get your access token from the [Account Tokens page](/dashboard/account/tokens), then save the script below to a file and make it executable:
+
+```bash
+chmod +x sync_supabase_config.sh
+```
+
+
+
+
+
+```bash
+#!/usr/bin/env bash
+set -euo pipefail
+
+# ---------------------------------------------------------------------------
+# Sync Supabase project configuration from a SOURCE project to a TARGET project
+# using the Supabase Management API.
+#
+# Usage:
+# export SUPABASE_ACCESS_TOKEN="sbp_..."
+# ./sync_supabase_config.sh [--dry-run]
+#
+# Options:
+# --dry-run Fetch and diff configs without applying changes to the target.
+#
+# Output:
+# Saves source and target configs to ./config_sync_/ so you can
+# review exactly what was (or would be) changed.
+# ---------------------------------------------------------------------------
+
+API_BASE="https://api.supabase.com/v1"
+
+SOURCE_REF="${1:?Usage: $0 [--dry-run]}"
+TARGET_REF="${2:?Usage: $0 [--dry-run]}"
+DRY_RUN="${3:-}"
+
+TOKEN="${SUPABASE_ACCESS_TOKEN:?Set SUPABASE_ACCESS_TOKEN environment variable}"
+
+AUTH_HEADER="Authorization: Bearer ${TOKEN}"
+CONTENT_TYPE="Content-Type: application/json"
+
+if ! command -v jq &>/dev/null; then
+ echo "Error: jq is required but not installed." >&2
+ exit 1
+fi
+
+# Configs to sync: "label|get_path|update_method|update_path"
+# pgbouncer is read-only so it is excluded.
+CONFIGS=(
+ "Auth|/config/auth|PATCH|/config/auth"
+ "Realtime|/config/realtime|PATCH|/config/realtime"
+ "Database Pooler|/config/database/pooler|PATCH|/config/database/pooler"
+ "Database Postgres|/config/database/postgres|PUT|/config/database/postgres"
+ "PostgREST|/postgrest|PATCH|/postgrest"
+ "Storage|/config/storage|PATCH|/config/storage"
+)
+
+# ---------------------------------------------------------------------------
+# Per-config payload transforms applied before PATCH/PUT.
+# Each function reads JSON from stdin and writes cleaned JSON to stdout.
+# ---------------------------------------------------------------------------
+transform_Auth() {
+ jq 'with_entries(
+ .key as $k |
+ select(
+ (["rate_limit_email_sent", "rate_limit_sms_sent",
+ "security_captcha_secret", "nimbus_oauth_client_secret",
+ "db_max_pool_size", "db_max_pool_size_unit",
+ "api_max_request_duration",
+ "sessions_single_per_user", "sessions_tags"
+ ] | index($k)) == null
+ and ($k | test("^(smtp_|sms_messagebird_|sms_textlocal_|sms_twilio_|sms_vonage_|sms_test_otp|hook_mfa_verification_attempt_|hook_password_verification_attempt_)") | not)
+ and ($k | test("passkey|web_?authn") | not)
+ and ($k | test("_secrets?$") | not)
+ )
+ )'
+}
+
+transform_Database_Pooler() {
+ jq 'if type == "array" then .[0] else . end
+ | {default_pool_size, pool_mode}
+ | with_entries(select(.value != null))'
+}
+
+transform_PostgREST() {
+ jq 'with_entries(select(.value != null))'
+}
+
+transform_Storage() {
+ jq 'del(.capabilities, .migrationVersion, .databasePoolMode, .features)'
+}
+
+transform_payload() {
+ local label="$1"
+ local func_name="transform_${label// /_}"
+ if declare -f "$func_name" &>/dev/null; then
+ "$func_name"
+ else
+ cat
+ fi
+}
+
+TMPDIR=$(mktemp -d)
+trap 'rm -rf "$TMPDIR"' EXIT
+
+OUTDIR="./config_sync_$(date +%Y%m%d_%H%M%S)"
+mkdir -p "$OUTDIR"
+
+echo "============================================="
+echo " Supabase Config Sync"
+echo " Source : ${SOURCE_REF}"
+echo " Target : ${TARGET_REF}"
+echo " Output : ${OUTDIR}"
+[[ "$DRY_RUN" == "--dry-run" ]] && echo " Mode : DRY RUN (no changes applied)"
+echo "============================================="
+echo
+
+for entry in "${CONFIGS[@]}"; do
+ IFS='|' read -r label get_path method update_path <<< "$entry"
+ safe_label="${label// /_}"
+
+ echo "--- ${label} ---"
+
+ # Fetch source config
+ source_file="${OUTDIR}/${safe_label}_source.json"
+ http_code=$(curl -s -o "$source_file" -w "%{http_code}" \
+ "${API_BASE}/projects/${SOURCE_REF}${get_path}" \
+ -H "$AUTH_HEADER")
+
+ if [[ "$http_code" != "200" ]]; then
+ echo " [SKIP] GET source failed (HTTP ${http_code})"
+ echo
+ continue
+ fi
+
+ # Fetch target config
+ target_file="${OUTDIR}/${safe_label}_target.json"
+ http_code_target=$(curl -s -o "$target_file" -w "%{http_code}" \
+ "${API_BASE}/projects/${TARGET_REF}${get_path}" \
+ -H "$AUTH_HEADER")
+
+ if [[ "$http_code_target" != "200" ]]; then
+ echo " [SKIP] GET target failed (HTTP ${http_code_target})"
+ echo
+ continue
+ fi
+
+ echo " [OK] Fetched both configs"
+
+ # Build transformed payload
+ payload_file="${TMPDIR}/${safe_label}_payload.json"
+ transform_payload "$label" < "$source_file" > "$payload_file"
+
+ # Skip if the transformed payload is empty (source had no usable config)
+ if [[ "$(jq 'length' "$payload_file")" == "0" ]]; then
+ echo " [SKIP] Source config is empty — nothing to apply"
+ echo
+ continue
+ fi
+
+ # Pretty-print both sides and diff
+ source_pretty="${TMPDIR}/${safe_label}_src_pretty.json"
+ target_pretty="${TMPDIR}/${safe_label}_tgt_pretty.json"
+ jq --sort-keys . "$payload_file" > "$source_pretty"
+ jq --sort-keys . "$target_file" > "$target_pretty"
+
+ if diff -q "$source_pretty" "$target_pretty" &>/dev/null; then
+ echo " [=] No differences"
+ else
+ echo " [~] Differences (source → target):"
+ diff --unified=3 "$target_pretty" "$source_pretty" | sed 's/^/ /' || true
+ fi
+
+ if [[ "$DRY_RUN" == "--dry-run" ]]; then
+ echo
+ continue
+ fi
+
+ # Apply to target
+ update_response="${TMPDIR}/${safe_label}_response.json"
+ update_code=$(curl -s -o "$update_response" -w "%{http_code}" \
+ -X "$method" \
+ "${API_BASE}/projects/${TARGET_REF}${update_path}" \
+ -H "$AUTH_HEADER" \
+ -H "$CONTENT_TYPE" \
+ -d @"$payload_file")
+
+ if [[ "$update_code" =~ ^2 ]]; then
+ echo " [OK] Applied to target (HTTP ${update_code})"
+ else
+ echo " [FAIL] ${method} failed (HTTP ${update_code})"
+ cat "$update_response"
+ echo
+ fi
+
+ echo
+done
+
+echo "Done. Configs saved to ${OUTDIR}/"
+```
+
+
+
+
+
+
+The script saves both source and target configs to a local `config_sync_/` directory so you can review exactly what changed. Use `--dry-run` to preview differences without applying them.
+
+## Related guides
+
+- [Migrating within Supabase](/guides/platform/migrating-within-supabase)
+- [Restore Dashboard backup](/guides/platform/migrating-within-supabase/dashboard-restore)
+- [Backup and Restore using the CLI](/guides/platform/migrating-within-supabase/backup-restore)
diff --git a/apps/docs/public/img/restore-after-90-day-dark.png b/apps/docs/public/img/restore-after-90-day-dark.png
new file mode 100644
index 00000000000..6a50f0a1c10
Binary files /dev/null and b/apps/docs/public/img/restore-after-90-day-dark.png differ
diff --git a/apps/docs/public/img/restore-after-90-day-light.png b/apps/docs/public/img/restore-after-90-day-light.png
new file mode 100644
index 00000000000..5fa5f49ff83
Binary files /dev/null and b/apps/docs/public/img/restore-after-90-day-light.png differ