# Import geozones from a CSV file

When you have hundreds of locations, such as every store in a chain or every pickup point, placing them on the map one at a time is not realistic. Import them from a spreadsheet instead.

## Prepare the file

The file needs a header row and one row per location, and must not exceed 5 MB. That is room for tens of thousands of locations, well past what a single geozone campaign is meant to cover. Three columns are required.

In the file you can use short headers such as `range` or `cooldown`. On the **Import geozones** screen the mapping dropdown uses the labels below (for example `range` → **Range, m**).

| Column in file | Maps to | Required | Notes |
|---|---|:--------:|-------|
| Name | **Name** | Yes | Up to 64 characters. |
| Latitude | **Latitude** | Yes | Between -90 and 90. |
| Longitude | **Longitude** | Yes | Between -180 and 180. |
| Range | **Range, m** | No | Meters, between 50 and 100,000. Falls back to **Default range, m**. |
| Cooldown | **Cooldown, s** | No | Seconds, between 60 and 3,596,400 (41 days). Falls back to **Default cooldown, s**. |
| Message | **Message** | No | Push text, up to 1024 characters. |
| Cluster code | **Cluster code** | No | An existing [cluster](/product/messaging-channels/push-notifications/geo-campaigns/clusters/) code. |
| Preset code | **Preset code** | No | An existing [preset](/product/content/push-presets/) code. |

Every geozone needs something to send, so each row must end up with either a message or a preset. It can come from the row's own column or from **Message for every zone** / **Preset for every zone** on the import screen. A preset wins when a row has both.

### Example CSV

Here is a `geozones.csv` with two locations. The second row leaves `range` empty so **Default range, m** applies:

| name | latitude | longitude | range | message |
| :--- | :--- | :--- | :--- | :--- |
| Store Downtown | 40.7128 | -74.0060 | 200 | Welcome to our store! |
| Pickup Point | 51.5074 | -0.1278 | | Your order is ready |

Representation of `geozones.csv` in plain text format:

```txt
name,latitude,longitude,range,message
Store Downtown,40.7128,-74.0060,200,Welcome to our store!
Pickup Point,51.5074,-0.1278,,Your order is ready
```

<Aside type="note" title="Exports from Excel work as they are">
The separator is detected from the file: comma, semicolon or tab. A comma is also accepted as the decimal separator in coordinates, so `55,7558` in a semicolon-separated export is read correctly. Quoted fields may contain the separator and line breaks.

Save the file as UTF-8. Excel offers this as **CSV UTF-8** in the **Save as** dialog. A file saved in a regional encoding instead shows non-Latin names as garbled characters. A byte order mark is fine. It is stripped on reading.
</Aside>

## How to import

Follow these steps to upload the file and create the geozones.

1. On the **Geo campaigns** page, click **Import CSV**. The **Import geozones** screen opens.

<img src="/push-notifications-geo-based-push-import-csv-button.webp" alt="Geo campaigns page header with the Import CSV button next to How to use."/>

2. Select your CSV file.

3. Map columns and set defaults on the **Import geozones** screen.

Map each CSV column to a geozone field. Under each column header, open the dropdown and choose the field that column should fill:

- If Pushwoosh matched the header wrong, pick the correct field in the dropdown.
- Set a column to **Do not import** to ignore it.
- Map **Name**, **Latitude**, and **Longitude** at minimum.
- Map **Range, m**, **Cooldown, s**, **Message**, **Cluster code**, or **Preset code** when those columns are in the file.

<Aside type="note">
Pushwoosh matches common header spellings automatically, such as `name`, `store`, `title`, `lat`, `latitude`, `lng`, `lon`, `longitude`, `radius`, `range`, `cooldown`, `message`, `cluster`, and `preset`.
</Aside>

Set defaults for values the file does not supply:

- **Default range, m** and **Default cooldown, s.** Used when the file has no range or cooldown column, or the cell is empty (as with "Pickup Point" in the example).
- **Cluster for every zone.** Pick a cluster, or leave **No cluster**.
- **Preset for every zone.** Optional push preset for every row that needs one.
- **Message for every zone.** Used when the file has no message column. Rows that already have a message keep their own text.

<img src="/push-notifications-geo-based-push-import-csv-mapping.webp" alt="Import geozones screen with columns mapped to Name, Latitude, Longitude, Range, m, and Message, plus Default range, m, Default cooldown, s, Cluster for every zone, Preset for every zone, and Message for every zone. Status shows 2 rows ready, 0 skipped."/>

4. Check the ready and skipped counts (for example **2 rows ready, 0 skipped**). Fix mapping or the spreadsheet if needed, then click **Check without importing**. Pushwoosh validates the whole file without creating geozones. [Rejected rows](#why-a-row-gets-skipped) come back with the line number and the reason, so you can fix the spreadsheet and try again with nothing left half-written.

5. When the check looks right, click **Import**. Geozones are created in batches. The result screen reports how many were created and which rows failed.

<img src="/push-notifications-geo-based-push-import-csv-result.webp" alt="Import geozones result screen showing how many geozones were created and which rows failed."/>

<Aside type="caution" title="Imported geozones are active">
Imported geozones are created active and start triggering pushes straight away. [Set a schedule](/product/messaging-channels/push-notifications/geo-campaigns/create-geozone/#schedule-when-the-campaign-can-fire) for each geozone after the import, or [pause them](/product/messaging-channels/push-notifications/geo-campaigns/manage/#manage-a-geozone) on the Geo campaigns list, if they should not fire yet.
</Aside>

Importing the same file twice creates a second copy of every geozone: names are not unique. To correct a mistake, delete the geozones you imported and import the corrected file.

### Why a row gets skipped

During check or import, rows that cannot be read are listed with their line number and skipped. The rest are still imported. A row is skipped when:

- the name is empty
- the latitude or the longitude is not a number, or falls outside the range given in the table above
- the range is not a number, or falls outside 50 to 100,000 m
- the cooldown is not a number, or falls outside 60 to 3,596,400 s
- the row ends up with neither a message nor a preset

To create geozones from your own system rather than a file, use the [addGeoZone API method](/developer/api-reference/geozones/#addgeozone).