Base 3:16
Guides

Migrating from another provider

Bringing customers and subscriptions across without billing anybody twice.

Base316 reads exports from Chargebee, Lago, Stripe, Clerk and Auth0, and its own CSV format.

The rule that matters

Importing a subscription must not bill it.

An account mid-cycle at its old provider has already paid for this period. So current_period_end is taken from your file, and the billing run picks the subscription up at its real renewal date — never on the day the import ran.

Recomputing the period from the import date would invoice a migrated customer twice in one month. That is the single most expensive mistake available here, and it is the one the importer is built around.

Order matters

  1. Rebuild your catalog by hand. Plans, features and meters are not imported — pricing models differ enough between providers that a translation would be a guess, and a wrong plan is worse than no plan.
  2. Import customers.
  3. Import subscriptions. They match customers by external_id, so a subscription for an unknown customer is reported rather than inventing one — which would hide a mis-ordered migration until the invoices came out wrong.

Every import previews first

The dashboard's Import & export tab checks the file against your real data and shows what would happen, row by row, before anything is written:

chargebee.csv — nothing written yet
CHARGEBEE
1 to create · 1 to update · 1 skipped · 2 with problems

Row  Outcome  Reason
4    SKIP     Repeated earlier in this file.
5    PROBLEM  A customer needs an external_id or an email to be matched on.
6    PROBLEM  "DOLLARS" is not a three-letter currency code.

Row numbers count the header as row 1, so they match your spreadsheet.

The mapping is shown, and can be corrected

Provider presets are a starting point, not an authority — providers rename columns between versions and exports differ by account settings. So the mapping is displayed before anything is written:

external_id      customer id
name             first name + last name
currency         preferred currency code
net_term_days    net term days

Not imported: vat number

Columns that matched nothing are named rather than dropped. If a column is mapped wrongly, override it and re-run — nothing has been written yet.

A file missing a required column fails once, up front, rather than once per row.

Re-running is safe

Nobody migrates in one attempt. A file is run against sandbox, corrected, run again, and finally run against production.

Rows are matched on external_id, so a row that already exists is updated, not duplicated. Existing subscriptions are skipped rather than updated — silently moving somebody's renewal date on a re-run is worse than reporting it.

Identity providers are different

Clerk and Auth0 have no billing to migrate, so their exports land on your users, not your customers. Mapping them onto customers would create billable accounts for people who were never billed.

Your users keep their passwords

This is the part most migrations get wrong, because most platforms make it impossible. Include a password_hash column and nobody has to reset anything: no reset email to your whole user base on a day they were not expecting one, and no churn among the people who ignore it.

bcrypt, argon2, PBKDF2 and bare MD5/SHA digests are all read as they are. Add password_hash_format if your export says which it used; otherwise it is detected from the shape of the hash.

email,name,password_hash
ada@globex.test,Ada Lovelace,$2a$10$…

Each hash is stored exactly as it arrived and replaced with Base316's own the first time that person signs in. After one login cycle a migrated table holds no foreign hashes at all.

A hash that cannot be read fails its row rather than importing the person without it. The alternative looks like success and produces somebody who can never sign in and is never told why. Run a dry run first — it reports those rows while your file is still open.

Auth0 releases password hashes on request, separately from the bulk user export, so the two are usually combined by hand into one file before importing. The column names vary depending on who did the combining, and the preset accepts the common spellings.

A row with no password_hash leaves an existing credential alone, so re-running an import to correct a name cannot lock anybody out.

Back-dated subscriptions

Two columns carry a migrated account's history:

  • started_at — when the account first subscribed. It becomes the subscription's creation date, so tenure, cohorts and revenue reports start where they really did. Chargebee's Started At and Stripe's Start Date (UTC) map to it.
  • current_period_start / current_period_end — the term it is in now.

If the file's current term has already ended — an export taken a while ago, or an account whose renewal the old system never ran — importing it as it stands would have the billing run invoice every period since, one renewal at a time. Such a row is refused, naming the option. Import with skipPastInvoices (the Don't invoice periods that already ended box on the import screen, or "skipPastInvoices": true on POST /v1/import/subscriptions) and the period is rolled forward along the plan's cadence to the one containing today, with no invoice for anything before it — the old provider billed those. The row says how many periods were rolled forward. The billing run then renews it at that period's real end, exactly like an account imported mid-term.

purchase_order_number, invoice_memo and billing_anchor_day set the subscription's contract terms; Chargebee's PO Number and Invoice Notes map to the first two.

Only live subscriptions import

ACTIVE and TRIALING. A cancelled subscription is skipped — importing one would create an entitlement for an account that is not entitled.

Getting your data out

Customers, subscriptions and invoices export as CSV from the same screen, or from the API:

GET /v1/export/customers
GET /v1/export/subscriptions
GET /v1/export/invoices

Real CSV with a Content-Disposition, so curl -O and a browser both do the obvious thing. Being able to leave is not a privileged action and export needs only read access.

On this page