# Restore orders

Source: https://developers.swell.is/app-guides/restore-orders

Restore orders finds orders that were deleted from your Swell store and restores them. Choose a period, review the orders deleted in it, and restore the ones you need with their original number and data.

The app reads the `order.deleted` events Swell records when an order is deleted, so it also finds orders deleted before you installed it. Restoring sends no emails, calls no webhooks and doesn't change your stock.

## How it works

| When | What the app does |
| --- | --- |
| You open Orders > Restore orders | Checks your access to orders and shows the search form, set to the last 30 days with Only not canceled on. It doesn't search until you click Search. |
| You click Search | Reads the store's order.deleted events in the period, takes the latest deletion of each order, and lists the orders that don't exist now and have a customer account. |
| You click an order | Shows the order's full order.deleted event below the grid. |
| You click Restore and confirm | Recreates each selected order from its event, with its original ID and number. An order that exists again is skipped, and a failure on one order doesn't stop the others. |
| The restore finishes | Shows how many orders were restored, lists the ones that weren't with the reason, and searches the same period again. |

The app runs only when someone uses the page. It has no functions, doesn't react to orders being deleted, and stores no records of its own.

## Before you begin

- An order can be found only through its `order.deleted` event. Swell records one when an order is deleted in the dashboard or through the API, unless the API request turns events off.
- An order can be found as long as its event exists. The app sets no limit on how far back you search, and events have no expiry date.
- The order must have a customer account. Orders without an `account_id` aren't listed.
- Test and live are separate environments, with their own orders and events. The app sees only the orders deleted in the environment it's opened in.
- Restoring needs **Manage** or **Full** access to orders. Staff with **View** access can search but not restore, and staff without access to orders can't use the page.

## Set up

Install Restore orders from the Swell App Store.

The app has no settings.

**1. Install it in the test environment**

Installs are per environment. Install the app in your test environment first. **Restore orders** then shows under **Orders** in that environment's dashboard.

**2. Delete a test order**

Place a test order, or open one you no longer need. Choose **Delete order**, enter the order number, and confirm.

**3. Find the order**

Go to **Orders > Restore orders** and click **Search**. The form starts with the last 30 days, so the order is listed, with today under **Date deleted**. Click it to see its event data.

**4. Restore the order**

Select the order, click **Restore 1**, and click **Restore 1 order** to confirm. The page shows "Restored 1 order.", and the order leaves the list. Open **Orders** to check that it's back with its original number.

**5. Go live**

Install the app in the live environment. It finds the orders deleted in live, including orders deleted before you installed it. Orders and events don't carry over from the test environment.

## Using Restore orders

### Who can search and restore

What a user can do on the page depends on the **Orders** level of their role, set in **Settings > Users** under **Manage roles**:

| Orders level | What the user can do |
| --- | --- |
| Manage or Full | Search, see event data and restore orders. |
| View | Search and see event data. The page says they can't restore, and Restore doesn't show. |
| Restrict | Nothing. The page shows an error, and Search stays off. |

Users without a role have **Full permissions** or a list of permissions. With **Full permissions** or **Orders** in the list, they can search and restore. Without **Orders**, the page shows the same error as for **Restrict**. The app checks the user's access with Swell on every request, not only when the page opens.

### Finding deleted orders

To find deleted orders, go to **Orders > Restore orders**, set **Date start** and **Date end**, and click **Search**. The dates are when the orders were deleted, not when they were placed. Both days are included, in your browser's time zone. The form starts with the last 30 days, and you can search any period.

The search reads 1,000 events per request. In a long period with many deletions, the page shows its progress, such as "Searching · events page 2 of 5". The line above the grid shows the total, such as "186 deleted orders found".

| Column | Value |
| --- | --- |
| Order number | The order's number, or its ID if it has none. A Canceled badge marks orders that were canceled when they were deleted. |
| Date created | When the order was created. |
| Date deleted | When the order was deleted. |
| Grand total | The order's grand total in its currency. |
| Account ID | The ID of the customer's account. |
| Full name | The shipping name, or the billing name when that's empty. |

The grid shows 10 orders per page. Dates and amounts follow your browser's language and time zone.

These orders aren't listed:

- Orders that exist now, because they were restored or recreated with the same ID.
- Orders without a customer account.
- Older deletions of an order deleted more than once. The latest deletion in the period decides whether the order is listed, and the restore uses that event. If the latest deletion is left out, an older one isn't listed instead.

### Only not canceled

**Only not canceled** is on by default and leaves out orders that were canceled when they were deleted, such as orders deleted with **Cancel and delete order**. To list them too, turn it off and click **Search**. They show with a **Canceled** badge.

### Sorting the results

To sort the results, choose **Date deleted** or **Date created** under **Sort by**. Orders are sorted newest first, and the default is **Date deleted**. Sorting applies at once, without searching again, and returns to the first page. Orders without that date go last. **Sort by** shows when there are two or more results.

### Viewing an order's event data

To see what an order held when it was deleted, click its row or its number. **Event data** opens below the grid with the full `order.deleted` event as JSON. The order is under `data`, with its items, addresses and totals. To hide it, click the order again or click **Close**.

The restore uses this data. Payments, shipments and other linked records are separate records, so they aren't in it.

### Selecting and restoring orders

To restore orders, select them with the checkboxes and click **Restore**. The button shows how many are selected, such as **Restore 2**, and the line above the grid shows "186 deleted orders found · 2 selected". The checkbox in the header selects the orders on the current page. Your selection stays when you change the page or the sort. To clear it, click **Clear selection**.

After you click **Restore**, the page asks you to confirm, such as "Restore 2 orders?", and says that each order is created again with its original ID, number and data, and that order emails and webhooks aren't sent. To start, click **Restore 2 orders**. To go back, click **Cancel**. A restore can't be undone from the app.

While the restore runs, the page shows its progress, such as "Restoring 10 of 25… Keep this page open until it finishes.", and the button shows **Restoring…**. The page sends 10 orders at a time, one group after another, so leaving the page stops the groups not sent yet. The page asks your browser to warn you before the tab is closed or reloaded, but going to another dashboard page doesn't warn you. Orders already restored stay restored and leave the list when you search again.

The result shows above the grid, such as "Restored 2 orders." If any weren't restored, it adds how many and lists each one with the reason, such as "Order 100551: The order already exists". The page then searches the same period again, so restored orders leave the list.

### Select all

**Select all** shows the number of results, such as **Select all 186**, and selects every result on all pages, including pages you haven't opened. It shows until every result is selected. Check the period and **Only not canceled** before you click **Restore**.

### What a restored order keeps

The app writes the order back from its `order.deleted` event, with its original ID.

- **Number and ID:** the original order number and ID.
- **Order data:** the items, addresses, totals, status, dates and other stored fields, as they were when the order was deleted. Nothing is recalculated, so prices, discounts, taxes and shipping aren't applied again.
- **Canceled orders:** an order that was canceled when it was deleted comes back canceled.
- **Payments and other records:** payments, refunds, shipments, returns, invoices and credits are separate records, and deleting an order doesn't delete them. They point to the order's ID, so they show on the restored order again.
- **Stock:** doesn't change. Deleting an order doesn't return its items to stock, and restoring it doesn't take them again. If the order was canceled before it was deleted, canceling returned the stock, and restoring leaves it there.
- **Customer:** the customer gets no email, and the order shows on their account again. Deleting and restoring don't update the account's order count and order value. Swell updates them at the customer's next order, or when one of their orders is canceled or its total changes.
- **Deleted customers:** deleting a customer also deletes their orders, and these orders are listed here. Restoring one doesn't bring the customer back, so the order keeps an `account_id` that no longer exists.
- **Events and notifications:** the restore creates no order events, such as `order.created`, `order.submitted` or `order.paid`. No notification emails go out, webhooks aren't called, and functions that run on those events don't run. Apps and webhooks that reacted to the deletion aren't told about the restore, so they still treat the order as deleted.
- **App hooks:** functions with a `before:` or `after:` hook on `order.created` still run, because hooks run during the write itself. A `before:` hook that refuses the order makes its restore fail, with the hook's message.
- **Sales:** a deleted order that wasn't canceled stays counted in your store's sales, and the restore doesn't count it again.
- **Subscriptions:** on a subscription without a separate order schedule, deleting an order takes it out of the subscription's invoice and payment totals, and restoring it doesn't put it back. The totals are updated the next time an order of the subscription is created or its payments change.
- **Channel:** isn't restored. Only Swell can set an order's channel, so **Channel** on the order page shows a dash.

### In the dashboard

| Where | What it's for |
| --- | --- |
| Orders > Restore orders | The search form, the deleted orders, their event data, and Restore. |
| Orders | Restored orders, with their original number. |
| Apps > Restore orders | The app's page, where you uninstall it. |
| Developer > Console > Events | The store's events, including order.deleted. |
| Developer > Console > Logs | The app's calls to the Swell API, such as GET /events and POST /orders. |

## Limitations

- An order can be restored only from an `order.deleted` event. An order deleted through the API with events turned off can't be found.
- Orders without a customer account aren't listed, so they can't be restored.
- An order deleted more than once can be restored only from its latest deletion in the period.
- The app sets no limit on the period. A long period with many deletions takes several requests, at 1,000 events per request.
- A restore sends 10 orders per request. A selection has no size limit, and the page sends the requests one after another, so closing it stops the orders not sent yet.
- Restoring doesn't send notifications, create order events, call webhooks or change stock.
- Only the order is restored. A customer deleted with it isn't.
- An order's totals and status aren't recalculated, and **Channel** isn't restored.
- Data that other apps stored on the order comes back only for apps installed at the time of the restore.
- The app has no undo. To remove a restored order, delete it again.

## Troubleshooting

The app shows its messages on the page. When a call to Swell fails during a search, **Error details** shows the call and Swell's response. The app writes no logs of its own. Its calls to the Swell API are under **Developer > Console > Logs**: choose the app under **App** to see them.

### An order isn't listed

Check that the dates cover the day the order was deleted, not the day it was placed, and that you're in the environment it was deleted in. If the order was canceled, turn off **Only not canceled**. Orders that exist now and orders without a customer account aren't listed. To check for the event, look for `order.deleted` under **Developer > Console > Events**.

### Search can't be clicked

**Search** is off while the page checks your access, while a search or a restore is running, while you confirm a restore, when a date is empty, and when **Date start** is after **Date end**. For users without access to orders, it stays off.

### "You don't have access to orders in this store."

Your role has **Restrict** for **Orders**, or you have no role and **Orders** isn't in your permissions. Ask the store owner for access to orders in **Settings > Users**, then reload the page.

### "You can only view orders in this store …"

Your role has **View** for **Orders**. You can search and see event data, but **Restore** doesn't show. To restore orders, you need **Manage** or **Full**. After your role changes, reload the page.

### "Open this app from the Swell dashboard to sign in."

The request had no dashboard session. Sign in to the Swell dashboard and open **Orders > Restore orders** there.

### "Unauthorized"

The dashboard session has ended or belongs to another store. Sign in again and open the page from this store's dashboard.

### "Invalid session"

The session cookie couldn't be read. Sign in to the dashboard again.

### "Swell request context is unavailable"

The request reached the app without the store's details from Swell. Open the page from **Orders > Restore orders**.

### "Unable to complete the platform request"

The app couldn't check the dashboard session or your access to orders with Swell. Try again in a minute. If it shows when the page opens, reload the page.

### "Forbidden"

A search or restore request didn't come from the app's own page. Open the page from **Orders > Restore orders** and try again.

### "Deleted order event not found"

The event no longer exists, or it isn't an `order.deleted` event. Search again.

### "The order already exists"

The order exists again, so it was skipped. Someone else may have restored it after your search. It leaves the list when the page searches again.

### "The event has no order data"

The event doesn't hold an order with a valid ID, so the order can't be restored from it.

### "The order was not restored"

Swell's answer had no order in it. Search again, and if the order is still listed, try again. The request shows as `POST /orders` in the logs.

### An order wasn't restored with another message

The text after the order number is Swell's answer. If it's a message such as "… is sold out.", a `before:` hook of an installed app refused the order. If every order in a group of 10 shows the same message, the whole request failed. Select those orders and try again.

### "Swell API … failed with HTTP …"

Swell answered a call with an error and no message. Try again in a minute. If it happens during a search, **Error details** shows the call and the response.

### "Request failed with status …"

The app got an answer it couldn't read. Try again in a minute.

### A customer's order count doesn't include a restored order

Restoring doesn't update the account's order count and order value. Swell updates them at the customer's next order, or when one of their orders is canceled or its total changes.

### A restored order has no channel

**Channel** isn't restored, so the order page shows a dash. The rest of the order isn't affected.

## Uninstalling

To uninstall the app, go to **Apps > Restore orders** and choose **Uninstall app**. This removes the app and its **Restore orders** entry under **Orders**. Uninstalling applies to the environment you are in, so remove the app from the test and live environments separately.

Restored orders stay, because they're regular orders. The app stores no records of its own, so nothing else is removed. `order.deleted` events stay too, so if you reinstall the app, it finds the orders you haven't restored.

## For developers

| Route | What it does |
| --- | --- |
| GET /app-api/admin/access | Returns orders, the user's access to orders: full for Manage or Full, or view for View. Users without access get 403. |
| POST /app-api/admin/search | Takes date_from, date_to, not_canceled and page. Reads one page of 1,000 events and returns page, pages, count, results, order_ids and done. |
| GET /app-api/admin/event?id= | Returns the full order.deleted event. Other event types return 404. |
| POST /app-api/admin/restore | Takes event_ids, up to 10, and restores each order. Needs full access, and returns 403 for view. Returns results, each with event_id, order_id, number, a status of restored, skipped or failed, and an error unless restored. |

The app is an admin app with `frontend.hosting: managed` and no functions. Swell hosts the page and its Worker. The sidebar entry comes from `content/restores.json`, with nav parent `orders` and link `frontend:///`. `models/restores.json` is a placeholder collection for that view and stores no records. Other `/app-api` paths return 404, wrong methods return 405, and all other paths serve the built page.

The Worker doesn't read `Swell-Context`. Each route reads the `_swell_admin_session` cookie and calls `GET /admin/api/session` on `Swell-Admin-Url` with it as `X-Session`. The session must have a `user_id`, and its `client_id` must match `Swell-Store-Id`. It then calls `GET /admin/api/user` the same way, and a missing user returns 401. The user's access to orders comes from `role.permissions.orders`: `restrict` is none, `view` is view, and any other value is full. Without a role, `all_permissions`, no `permissions` list, or `orders` in `permissions` is full, and anything else is none. None returns 403 on every route, and restore returns 403 unless access is full. POST routes also need an `Origin` equal to the request's origin and a JSON `Content-Type`, and otherwise return 403.

Calls to Swell go to `Swell-API-Host` with Basic auth from `Swell-Store-Id` and `Swell-Access-Token`, the installation's key, which is per environment. `permissions` in `swell.json` is empty, which gives the key full Backend API access. The Worker calls only `GET /events`, `GET /events/{id}`, `GET /orders` and `POST /orders`. The key is removed from error details, and responses are `Cache-Control: private, no-store`.

Search reads `GET /events` with `where[type]=order.deleted`, `$gte` and `$lt` on `where[date_created]`, `sort=date_created desc, id desc`, `limit=1000` and the fields the grid needs. It keeps the first event of each `data.id`, drops events with no `data.account_id` and, with `not_canceled`, events with `data.canceled: true`, and checks the rest with `GET /orders` and `where[id][$in]`, 100 IDs at a time. `order_ids` lists every order on the page, so the browser can skip older deletions on later pages. The browser asks for pages until `done`. `date_from` and `date_to` are the start of **Date start** and the start of the day after **Date end**, in the browser's time zone.

An `order.deleted` event holds the order record as it was stored, without linked records. Events have no TTL. The 90-day TTL in the events indexes applies to their webhook delivery records.

Restore reads `GET /events/{id}`, checks `GET /orders` for the order's ID, and calls `POST /orders` with the event's `data` plus `$restore: true` and `$events: false`. `id`, `number`, `date_created` and `date_updated` are stored as sent.

- `$restore` skips validation, including the unique check on `number`, formulas, model triggers, link writes and notifications. The triggers cover stock, taxes, promotions, and account and subscription updates.
- `$events: false` skips event capture, so no webhooks or asynchronous functions run and no activities are recorded.
- `before:` and `after:` hook functions aren't skipped. Those on `order.created` run, and so do those on declared order events whose conditions the write matches.
- The platform drops `channel` and `attribution` from the write, and `stamp_channel` doesn't run, so the order has neither.
- `GET /events/{id}` returns `$app` data only for installed apps, so data of an app uninstalled since the deletion isn't written back.

The Worker writes no logs. When a Swell API call fails during a search, the route returns `{ error, details }` with status 502, and the page shows `details` under **Error details**. In a restore, each order's failure is in its result's `error`. The app's own messages, such as for invalid input, return only `{ error }`. API calls made with the app's key are logged as requests, with the app's ID.

## Support

Swell builds and maintains the Restore orders app. For help with the app, email support@swell.is.
