Skip to content

Import existing customers and work

Moving to WorkWing from another system, or from a spreadsheet, means bringing across your customers, the sites you visit for them and the work that’s still to do. Customers and jobs import from a CSV file, and the API is there for very large or repeated syncs. This guide puts the pieces in order so each step has what it needs from the one before.

What How it comes in
Job types You build them in WorkWing
Custom fields You set them up in WorkWing
Your people Each one accepts an invitation
Customers A CSV file through Settings > Data Import, or through the API
Locations Each customer’s main site from its address; other sites from job addresses, or through the API
Jobs, open and past A CSV file through Settings > Data Import, or through the API
Recurring visits Set up again in WorkWing, or through the API

People still come in by invitation, not by import. A job import links each job to a customer that already exists, which is why customers come first.

If you have questions about a move, email support@workwing.io.

  1. Build your job types. Every job needs one, and a job import takes its columns from the job type you pick. See Creating a job type.
  2. Add custom fields for anything your old system tracks that WorkWing has no box for: an account number, a contract reference, an asset tag. Set them up before you import, so the job import offers a column for each job field and each custom field, and the customer import offers one for each customer field. See Custom fields.
  3. Decide which brand each customer belongs to, if you run more than one. A customer belongs to one brand and can’t be moved later, and an API key works in one brand only. The CSV import puts a whole file into the brand you choose at its first step, so split your export by brand and import each file into its own.
  4. Export from your old system: your customers with their sites, and the work that’s still open or booked. Keep each customer’s reference from the old system. You’ll use it twice.

People can’t be imported, because each person signs in with their own account. Open Settings > Team Members, click Invite colleague and choose the Field worker role. When they accept, WorkWing creates their resource record and they can sign in to the mobile app. Subcontractors come in through Invite external worker instead. See Adding a resource and External workers.

Do this early. Invitations wait on other people, and you’ll want your engineers on the board before you start assigning imported work.

Open Settings > Data Import and choose Customers. Data import covers the steps: choose the brand, upload your CSV file, map its columns, choose a match column, preview every row, then import. One file holds 5,000 rows at most.

Map your export’s ID column to Customer reference and WorkWing matches on it, so running the file again skips customers that are already there. Choose Update the existing customer instead if you want a second pass to fill gaps. It writes only the cells that have something in them.

A customer’s main location is made from their address during the import, and the address is looked up on the map in the background, so the map pin follows shortly afterwards. To add customers one at a time, use Create Customer. Adding a customer covers the form.

Whichever way you create them, put the old system’s reference in External CRM ID, under Advanced on the customer form. The job import uses it to tell apart two customers with the same name, and the customer import and an API script use it to find a customer again without making a second one.

For more than 5,000 customers, or a sync you will run again and again, write a short script against the API. Create a key in Settings > API Keys for the brand the customers belong to, ticking read, create and update for customers, and read and create for locations. Getting started with the API covers keys, the idempotency header and the error shape.

Each customer takes at least two calls:

  1. Look the customer up by your reference, and skip it if it’s already there.
  2. Create the customer.

When the customer has an address, WorkWing makes their primary location from it two minutes after the create. (The form and the CSV import make it straight away.) Only add the next two calls for extra sites:

  1. Create a location for each additional site.
  2. Set the main site as the customer’s primary location, if it is not the one made from the address.

The example below creates the site itself, so the primary location is in place straight away.

Each create takes one record. Build the Idempotency-Key from your own reference, so running the script again within 24 hours replays the first answer rather than creating a duplicate. The lookup in step 1 covers runs after that.

const API = "https://api.workwing.io/v1";
const headers = {
Authorization: `Bearer ${process.env.WORKWING_API_KEY}`,
"Content-Type": "application/json",
};
async function call(method, path, body, idempotencyKey) {
const res = await fetch(`${API}${path}`, {
method,
headers: idempotencyKey
? { ...headers, "Idempotency-Key": idempotencyKey }
: headers,
body: body ? JSON.stringify(body) : undefined,
});
const json = await res.json();
if (!res.ok) throw new Error(`${json.error.code}: ${json.error.message}`);
return json.data;
}
async function importCustomer(row) {
const ref = encodeURIComponent(row.ref);
const found = await call("GET", `/customers?external_crm_id=eq.${ref}&select=id`);
if (found.length > 0) return found[0].id;
const [customer] = await call(
"POST",
"/customers",
{
name: row.name,
customer_type: "T_ORGANIZATION",
email: row.email,
phone: row.phone,
external_crm_id: row.ref,
external_crm_type: "old-system",
},
`import-customer-${row.ref}`,
);
const [location] = await call(
"POST",
"/locations",
{
customer_id: customer.id,
address_line1: row.address1,
locality: row.town,
postal_code: row.postcode,
country: "GB",
location_notes: row.accessNotes,
},
`import-location-${row.ref}-1`,
);
await call(
"POST",
`/locations/${location.id}/set-primary`,
undefined,
`import-primary-${row.ref}`,
);
return customer.id;
}

A location’s town goes in locality, not city as it does on the customer.

Your plan’s rate limit sets the pace. At four calls a customer, 1,000 customers is 4,000 calls, which takes just over an hour on Free at 60 requests a minute and under seven minutes on Standard. When a call answers 429, wait for the number of seconds in its Retry-After header and try it again. Handling API errors shows how to tell the errors worth retrying from the ones that need fixing first, and Nightly customer sync turns the same pattern into a job that keeps WorkWing in step with your CRM afterwards.

Choose Jobs on Settings > Data Import. It creates jobs from a CSV file, and it’s the quickest way to bring a schedule across, finished work included. These are the points that shape how you prepare the file:

  • One brand and one job type per file. Every job in one import goes into the brand you choose first and shares a job type from that brand, so split your export by brand and by kind of work and import each file separately.
  • Customers match by reference, then name. Each row’s customer reference is tried against the External CRM IDs of the customers in that brand, and the customer name is used when there is none. Where two customers share a name, use the reference.
  • Sites match by address. A row whose address matches one of the customer’s locations uses it. If several match, the preview asks you to choose. A row that matches nothing creates a new location for that customer, and the preview flags it.
  • Projects first. To put jobs into a project, create the project first and give its job number in Parent job number.
  • Statuses. A job can come in as pending, scheduled, completed or cancelled. Scheduled needs a start, and completed needs a completed date. That is how you load your finished history.
  • Nobody is assigned. The file has no column for who does the work, so assign people afterwards from the dispatch console. Imports never assign anyone automatically.
  • Quiet. Imports send no emails, texts or webhooks, so loading old jobs does not message your customers.
  • Dates. Write them as 2026-03-15, 2026-03-15 09:30 or day first as 15/03/2026. A date or time without a time zone is read in the brand’s time zone; one with an offset keeps it.
  • Old job numbers. Map them to Job reference. It becomes the match column unless you choose otherwise, so running the file again skips jobs that are already in. Jobs are never updated by an import.

Creating a job that nobody is on yet doesn’t count against your plan’s job allowance. A job counts the first time you assign someone to it. See Plan limits and overage.

To create jobs through the API instead, you need each job’s job type, customer and location, and you can assign someone in the same call. The API calls job types job templates: list them with GET /v1/job_templates. Create and schedule a job walks through the calls, and Safely create jobs with idempotency covers re-running an import without doubling it.

Visits that repeat, such as a quarterly service, are better set up as a recurring schedule than imported one by one. Create one job for the customer, get it right, then click Make Recurring. See Recurring jobs. The API can create schedules too: POST /v1/job-schedules, described in the API reference.

  • Map pins. Locations created by an import or through the API arrive without a map pin, including the main locations made from customer addresses. WorkWing looks each address up in the background. A site it can’t find stays without a pin, won’t show on the dispatch map and can’t be routed to. Check the sites you’re sending people to first, and for any without a pin, open the location and use Find on map. See Managing locations.
  • A sample of customers. Open ten and compare them with your old system: name, contacts, primary location, access notes, custom fields.
  • The schedule. Open the dispatch console on your first live day and check the imported jobs sit where you expect them.