Skip to content

Data migration

SettingsMay 13, 2026·18 min read·Updated November 11, 2026

Data migration brings your existing business data into Off The Couch from a previous system. Each migration type has its own CSV schema with required and optional columns. Prepare the files according to the schemas in this article, then send them to your Off The Couch migration administrator.

Getting started

Navigate to Settings > Data migration from the sidebar.

How it works

Migrations are performed by Off The Couch administrators only. The page shows a notice at the top of the Migration upload tab:

Currently Migrations can only be performed by Off The Couch Administrators. Please contact Nick at nick@offthecouch.io or on Discord to schedule your migration. In preparation please look at Migration Advice to the right and the Migration Types below to prepare reports in advance.

The CSV upload control is hidden for non-OTC accounts. Your job is to prepare CSV files using the schemas below and send them along; Nick (or another Off The Couch admin) runs the actual import on your behalf and the results show up in the migration log on this page.

The page has two tabs:

TabPurpose
Migration uploadThe migration type picker, schema panel, and (for OTC admins) the upload control. The right side shows the Migration Advice panel
Migration logOne row per past migration run, with success / failure / duplicate counts, a results total, a download of the log list as CSV, and per-run actions (view records, view duplicates, download error records, purge records)

Migration Advice

The page surfaces four pieces of generic advice on the right side of the Migration upload tab. They apply to every migration:

  1. Recommended order of file uploads: 1. Customers (Global), 2. Transactions (By Group / Location), 3. Bookings (By Group / Location, needs to be associated to a transaction by an ID), 4. Waivers (By Group / Location), 5. Gift cards, 6. Customer credit (Global), 7. Promo codes (By Group / Location).
  2. Make sure your headers match the ones provided. Use the copy tool on the page (or the schemas in this article) to avoid typos.
  3. Headers marked with * are required. Every other column is optional.
  4. Make sure you have corresponding events created before uploading bookings or waivers and that the names match.

Headers are case-sensitive. "Email" works; "email" or "EMAIL" doesn't. Match the casing exactly as shown in this article.

Step-by-step guide

Prepare your files

  1. Look at the Migration Advice above and follow the recommended order
  2. Pick the migration type you're starting with from the schemas below
  3. Build a CSV with the headers from the schema. Use the copy icon on the Settings > Data migration page to copy the field list and avoid typos
  4. Make sure required fields (marked with *) have a value in every row
  5. Confirm your event names in Events > Event settings match the names in Booking name and waiver columns (corresponding events must exist before bookings or waivers can be migrated)

Send your files for migration

  1. Once your CSVs are ready, contact Nick at nick@offthecouch.io or on Discord (per the notice on the page)
  2. Share your prepared CSV files with him
  3. Off The Couch administrators run the import on your behalf
  4. The result lands in the Migration logs tab on this page

Review the migration log

Open the Migration logs tab on Settings > Data migration. Each previous migration run is one row with these columns:

ColumnWhat it shows
Upload typeMigration type as a badge
Uploaded byTeam member who started the run, plus the timestamp
StatusA badge for the run's state: queued, running, completed, cancelled, failed, or purged
Records importedSuccessful rows imported so far over the rows in the file (e.g., "150 / 200"). While the run is in progress a progress bar underneath shows which batch is being processed (e.g., "batch 2 of 3"). Shows a dash once the run is purged
Failed recordsRows that didn't import due to validation errors. Shows 0 when every row imported
DuplicatesRows skipped because they matched existing records
ActionsThree-dot menu with Cancel run (while the run is queued or running), View records, View duplicates, Download error records, Purge records

The filter bar above the table narrows the list by Upload type, Status, Uploaded by, and Date. Filters can be saved as views and reopened from the saved views drawer. Next to the filters are the number of matching runs, a cloud icon that downloads the listed runs as a CSV (type, uploaded by, date, time, totals, and purged status), and the per-page selector (5, 10, 25, or 50). The empty state ("No data has been uploaded yet.") appears when no migrations have been run.

Runs are processed one batch at a time, and the system checks how busy the database is before every batch. When the database is under load, the run waits and resumes on its own, so a large file can take a while without slowing down bookings or checkout. The list refreshes itself every few seconds while a run is in progress.

Inspect a previous run

  1. In the migration log, open the three-dot menu on the run
  2. Pick the action you need:
ActionWhen
Cancel runStop a run that is queued or running. Rows already written stay in place and can be removed afterwards with Purge records. Requires the Delete migrations permission or the permission used to start migrations
View recordsSee the records imported by the run
View duplicatesSee the rows that were skipped because they matched existing records (only present when duplicates were found)
Download error recordsGet a CSV of the failed rows with their error reason. Fix and resend (only present when there were failures)
Purge recordsPermanently remove every record imported by this specific run. For an Update customers run, restore every updated profile to its previous values instead. Requires the Delete migrations permission. Hidden while the run is queued or running; cancel the run first

Cancel a running migration (OTC Only)

  1. In the migration log, open the three-dot menu on a run whose status is queued or running
  2. Click Cancel run
  3. Confirm the Cancel this run? prompt

The run stops after the batch that is currently being written. Its status changes to cancelled, and the rows written before the stop stay in place until the run is purged. If the run finishes in the same moment the cancel is requested, the page reports "Import already finished" and the status stays completed.

Purge a migration run (OTC Only)

  1. In the migration log, open the three-dot menu on the run you want to remove
  2. Click Purge records
  3. Confirm the Purge records? prompt
  4. Every record imported by that specific run is deleted. For an Update customers run, every profile the run changed goes back to its previous values instead

Purging is permanent. Records that already existed before the run are not affected. The row shows a purging badge while the purge runs and a purged badge once it finishes.

Schemas

Customers (Global)

Customer records. Migrated once per account, not per group.

FieldRequiredDescription
First nameNoCustomer's first name
Last nameNoCustomer's last name
EmailYesUsed as the unique customer identifier
BirthdayNoDate of birth in YYYY-MM-DD format
PhoneNoPhone number
Address line 1NoStreet address
Address line 2NoApartment, suite, or unit
CityNoCity
StateNoState or province
ZipNoZIP or postal code
CountryNoTwo-letter ISO 3166-1 alpha-2 code (US, CA, GB, AU, DE, NZ). The page has a Download reference link for the full country code list
Email opt outNoWhether the customer has opted out of marketing emails. Boolean: 1 / 0 or true / false
Photo opt outNoWhether the customer has opted out of photo sharing. Boolean: 1 / 0 or true / false
Date joinedNoWhen the customer first interacted with your business, in YYYY-MM-DD. Defaults to the import date if omitted
Date last visitedNoDate of the customer's most recent visit, in YYYY-MM-DD. Defaults to the import date if omitted
NotesNoFree-text internal notes about the customer
TagsNoTag names separated by commas, wrapped in double quotes, for example "VIP, Birthday club". Tags that are not in the importing location's tag library yet are added to it. Each tag can be up to 100 characters

A row whose email already has a customer profile is counted as a duplicate and left unchanged. Duplicate rows do not receive tags. To correct profiles that were already imported, use the Update customers migration below.

Update customers (Global)

Corrects customer profiles that were created by an earlier Customers migration, for example an opt out setting that was imported wrong, and adds tags to them. Rows are matched to existing profiles by email. No profiles are created or deleted.

Only the columns included in the file are updated. Leave a cell blank to keep the current value. Rows whose email does not match exactly one profile created by a Customers migration are listed in the error records with a reason, and nothing changes for that row.

FieldRequiredDescription
EmailYesFinds the customer profile to update. The email itself is not changed
First nameNoCustomer's first name
Last nameNoCustomer's last name
BirthdayNoDate of birth in YYYY-MM-DD format
PhoneNoPhone number
Address line 1NoStreet address
Address line 2NoApartment, suite, or unit
CityNoCity
StateNoState or province
ZipNoZIP or postal code
CountryNoTwo-letter ISO 3166-1 alpha-2 code (US, CA, GB, AU, DE, NZ)
Email opt outNoWhether the customer has opted out of marketing emails. Boolean: 1 / 0, true / false, or yes / no
Photo opt outNoWhether the customer has opted out of photo sharing. Boolean: 1 / 0, true / false, or yes / no
Date joinedNoWhen the customer first interacted with your business, in YYYY-MM-DD
Date last visitedNoDate of the customer's most recent visit, in YYYY-MM-DD
TagsNoTag names separated by commas, wrapped in double quotes, for example "VIP, Birthday club". Tags are only added: a customer keeps the tags they already have. Tags that are not in the uploading location's tag library yet are added to it. Each tag can be up to 100 characters

Tags are matched to the library without regard to capitalization, so "vip" adds the existing VIP tag, and an archived tag with the same name is restored. A customer who already has a tag is not given it twice. A row with tags is listed in the error records with "Customer is not linked to this location" when the location you upload from does not appear in the customer's Groups column on the Customers page, and nothing changes for that row.

View records on an Update customers run shows each profile as it is now, plus a Previous values column listing what the run replaced and the tags it added. Purging the run restores those values and removes those tags; the tags stay in the tag library.

Transactions (By Group / Location)

Order-level records. Scoped to the company group you're migrating into.

FieldRequiredDescription
First nameNoCustomer's first name on the transaction
Last nameNoCustomer's last name
EmailYesCustomer email used to link the transaction to a customer record
PhoneNoCustomer phone
Transaction dateNoWhen the transaction was created. Format YYYY-MM-DD HH:MM:SS or YYYY-MM-DD (omit time if not known)
Transaction statusNoOne of active or cancelled
Order numberYesYour existing order/invoice number. Used as the unique identifier and to link bookings to this transaction
PriceNoSubtotal before taxes, fees, or discounts
TotalNoTotal amount on the transaction
PaidNoAmount already paid
DueNoOutstanding balance
TaxesNoTax amount
FeesNoFee amount
DiscountNoDiscount amount applied
RefundedNoAmount refunded
NotesNoFree-text internal notes

Bookings (By Group / Location)

Booking slots and reservations. Migrate after Transactions so each booking can be linked to its transaction by Order number.

FieldRequiredDescription
Booking nameYesName of the event corresponding to this booking. Must match an existing event in Events > Event settings
Booking start dateYesDate the booking starts, in YYYY-MM-DD
Booking end dateNoDate the booking ends, in YYYY-MM-DD
Booking start timeYesBooking start time in HH:MM:SS
Booking end timeYesBooking end time in HH:MM:SS
Group sizeNoNumber of participants in the booking
PriceNoPrice for the booking
StatusYesSlot status. One of available, booked, cancelled, blocked, call_to_book
Order numberNoThe corresponding transaction's Order number, when applicable. Links the booking to a transaction in your data
NotesNoFree-text booking notes
Reservation typeNopublic for public bookings (multiple groups can book the same slot, no duplicate check applied) or private for private bookings

Waivers (By Group / Location)

Signed waivers tied to a customer and an event. Migrate after Bookings so the corresponding events exist.

FieldRequiredDescription
First nameNoSigner's first name
Last nameNoSigner's last name
EmailYesSigner's email used to link to the customer record
BirthdayNoSigner's date of birth in YYYY-MM-DD
PhoneNoSigner's phone
Date signedNoWhen the waiver was signed. Format YYYY-MM-DD HH:MM:SS or YYYY-MM-DD (omit time if unknown)
Booking nameYesName of the event the waiver is for. Must match an existing event in Events > Event settings
Booking start dateYesBooking date in YYYY-MM-DD
Booking end dateNoBooking end date in YYYY-MM-DD
Booking start timeYesBooking start time in HH:MM:SS
Booking end timeYesBooking end time in HH:MM:SS

Gift cards

Gift card records.

FieldRequiredDescription
Gift card codeYesThe unique gift card code your customers redeem
Personal messageNoPersonal message attached to the gift card
Date purchasedNoPurchase date in YYYY-MM-DD
Delivery dateNoScheduled delivery date in YYYY-MM-DD
Expiration dateNoExpiration date in YYYY-MM-DD
StatusNoactive or deactivated. Defaults to active when omitted
Total valueYesTotal amount loaded on the gift card
SpentYesAmount already spent (0 for unused cards)
Order numberNoLinks the gift card as a purchase under an existing transaction with this Order number
Customer first nameNoPurchaser first name
Customer last nameNoPurchaser last name
Customer emailYesPurchaser email used to link to the customer record
Recipient emailYesEmail of the gift card recipient
Recipient first nameNoRecipient first name
Recipient last nameNoRecipient last name

Customer credit (Global)

Store-credit balances on customer accounts. Migrated once per account.

FieldRequiredDescription
First nameNoCustomer's first name
Last nameNoCustomer's last name
EmailYesCustomer email used to link the credit to the customer record
Total valueYesTotal credit issued
RemainingNoRemaining credit balance
SpentNoAmount of credit already spent

Promo codes (By Group / Location)

Promo and discount codes.

FieldRequiredDescription
Promo nameYesThe promo code itself (the string customers redeem)
DescriptionNoInternal description of the promo
Redemption typeYesvalue for fixed-amount discounts (e.g., $10 off) or percentage for percent-based discounts (e.g., 10% off)
Value amountYesThe numeric discount value (e.g., 10 for $10 off or 10%)
QuantityNoTotal redemptions allowed. Set to -1 for unlimited
Number of times usedNoExisting usage count to carry over
Applicable to gift cards and merchandiseNoWhether the promo can apply to gift cards and merchandise. Boolean: 1 / 0 or true / false
Promo code groupNoOptional. Links promo codes to an existing promo code template by name

Reference

Date and time formats

Field typeFormat
Date-only fields (Birthday, Booking start date, Date purchased, etc.)YYYY-MM-DD
Date-time fields (Date signed, Transaction date)YYYY-MM-DD HH:MM:SS or YYYY-MM-DD if you want to omit the exact time
Time-only fields (Booking start time, Booking end time)HH:MM:SS

Dates and times in a file are read as the local time of the location you migrate into, so a Date joined of 2026-01-10 shows as January 10 at that location.

Boolean values

Boolean columns accept 1 / 0, true / false, or yes / no, in any letter case. Any other value puts the row in the error records. In the Update customers schema a blank boolean cell keeps the current value; in every other schema a blank cell means 0.

Enum values

FieldAllowed values
Status (Bookings)available, booked, cancelled, blocked, call_to_book
Reservation type (Bookings)public, private
Transaction statusactive, cancelled
Redemption type (Promo codes)value, percentage
Status (Gift cards)active, deactivated
Country (Customers)Two-letter ISO 3166-1 alpha-2 code

Migration log row actions

ActionShown when
Cancel runThe run is queued or running, and the user has the Delete migrations permission or the permission used to start migrations
View recordsAlways
View duplicatesThe run had duplicate records skipped
Download error recordsThe run had failed records
Purge recordsThe run is not queued or running, and the user has the Delete migrations permission

Migration log statuses and counts

ElementDescription
Status badgequeued (waiting for its first batch), running, completed, cancelled, failed (the run could not start or finish), purging, or purged
Records importedSuccessful row count over the rows in the file, with a batch progress bar while the run is in progress
Failed recordsCount of rows that didn't import
DuplicatesCount of rows skipped because they already existed
Dash in Records importedShown when the run has been purged
Results totalNumber of migration runs matching the current filters, shown next to the download icon and per-page selector

Empty state

If no migrations have been run yet, the migration log table shows the warning triangle icon and "No data has been uploaded yet."

Good to know

  • Migrations are performed by Off The Couch administrators only. The upload control on the page is hidden for non-OTC accounts. Send your prepared files to nick@offthecouch.io or on Discord to schedule a run.
  • Headers are case-sensitive. "Email" works; "email" or "EMAIL" doesn't. Use the copy icon on the page (or the schemas in this article) to grab the exact header text and avoid typos.
  • The recommended file order in Migration Advice is the safe path: Customers, Transactions, Bookings, Waivers, Gift cards, Customer credit, Promo codes. Following the order means each migration has the data it needs to link records correctly (e.g., bookings link to transactions by Order number, waivers link to bookings by event name).
  • Customers, Update customers, and Customer credit are global. The other migration types are scoped to the company group you're migrating into. Switch groups before sending the data if you have multiple locations. Customers brought in by a migration appear in the customer list of the group the migration was run from.
  • Update customers only touches profiles that a Customers migration created. Profiles that came from bookings, waivers, or check-ins are skipped and listed in the error records, so a bulk correction can never overwrite organically collected data.
  • Bookings and Waivers require corresponding events in Events > Event settings. Create the events first; they're matched by exact name in the Booking name column.
  • Country for the Customers schema must be a two-letter ISO 3166-1 alpha-2 code, not the full country name. Examples: US, CA, GB, AU, DE, NZ. The page has a Download reference link for the full code list.
  • Order number in the Bookings schema is what links a booking to its transaction. Make sure the Transactions migration ran first and that the same Order number is used in the Bookings file.
  • Public bookings (Reservation type = public) are not checked for duplicates because the same slot can host multiple parties.
  • Promo codes: set Quantity to -1 to allow unlimited redemptions. Existing usage counts can be carried over via Number of times used.
  • Failed rows are downloadable as CSV via Download error records in the migration log's three-dot menu. Each failed row has its error reason in a column. Fix the issues and re-send only the corrected rows.
  • One run at a time per location. A new file for the same company group is refused with "An import is already running for this location. Wait for it to finish or cancel it." until the current run completes or is cancelled.
  • Cancelling keeps what was written. Cancel run stops the run; it does not undo it. Use Purge records afterwards to remove the rows that made it in.
  • Purging removes only records imported by that specific run. It doesn't affect records that already existed before the run. Purging an Update customers run restores the previous values and removes the tags it added instead of deleting anything.

FAQ

Q: A run has been sitting at the same batch for a while. Is it stuck?

A: Usually not. The run pauses whenever the database is busy and continues on its own, and the status badge stays running the whole time. A run whose status is failed did stop; check the error records, fix the file, and send it again.

Q: Can I upload migrations myself?

A: No, not yet. The page restricts uploads to Off The Couch administrators. Prepare your files using the schemas in this article and send them to nick@offthecouch.io or contact Nick on Discord. He'll run the imports for you.

Q: Some customers were imported with the wrong opt out setting. How is that corrected?

A: Prepare a file for the Update customers migration with just the Email column and the column to correct, for example Email opt out, and send it for migration. Only the columns in the file change. If the correction turns out to be wrong, purging that run restores the earlier values.

Q: I got the email wrong but the rest of my CSV is fine. What now?

A: Once a migration has run, the imported records can be purged via the Purge records action in the migration log. After purging, fix the email column and resend the corrected file.

Q: My file failed to upload entirely. What do I do?

A: Confirm the file is .csv (not .xlsx), the headers match exactly (case-sensitive), and the date and time formats match the Date and time formats reference. Re-export from your spreadsheet and resend.

Q: Some rows imported but others failed. Where are the failures?

A: In the Migration logs tab, open the three-dot menu on the run and click Download error records. The downloaded CSV includes a column explaining why each row failed. Fix and resend only the failed rows.

Q: Can I undo a migration?

A: Yes. Use Purge records in the migration log's three-dot menu. This deletes every record imported by that specific run. Records that already existed before the run are untouched.

Q: Why do my booking imports fail with an "event not found" error?

A: The Booking name column must exactly match an event in your account. Create the events first under Events > Event settings, then resend the bookings file.

Q: Why do my waiver imports fail?

A: Waivers require both the customer (matched by Email) and the event (matched by Booking name) to exist. Run the Customers migration first, then create matching events, then send the waivers.

Q: Why is the Purge records option missing from a row's menu?

A: It requires the Delete migrations permission. Ask an admin to grant it in Settings > User management.

Q: What does "By Group / Location" mean in the migration order?

A: That migration type is scoped to a single company group. If you have multiple locations, you'll need a separate file per group, with the right group selected in the group picker before sending each one.

Q: My country names are full names like "United States". Will the migration accept them?

A: No. The Country column must be a two-letter ISO 3166-1 alpha-2 code (e.g., US). Convert before sending. The page has a Download reference link with the full country-to-code mapping.

Something wrong or out of date?