# Update pass using flat files

The Wallet Crew can update existing Apple Wallet and Google Wallet passes from a CSV file. This is useful for bulk updates and for legacy systems that can only export flat files.

{% hint style="warning" %}
SFTP imports are an optional feature. Ask The Wallet Crew to enable SFTP on your tenant before implementing this flow.

Treat SFTP imports as a **last resort**. Avoid this flow unless you have no other option (for example, a legacy system that can only export flat files).

For most projects, the API is the recommended approach. It is easier to automate, easier to monitor, and updates in real time.

If you prefer a real-time integration, use the API update flow instead. See [leveraging-updated-pass-data-with-the-wallet-crew-api](https://docs.thewalletcrew.io/develop/guides/wallet/leveraging-updated-pass-data-with-the-wallet-crew-api "mention").
{% endhint %}

<details>

<summary><strong>Real-world examples</strong></summary>

* A gift card program recalculates balance every hour. A flat file export updates `additionalData.balance` for all active cards.
* A marketing automation team exports a CSV to prepare a push campaign. Each row updates `additionalData.notification_content` before sending notifications.
* A ticketing system exports last-minute changes (gate, seat, or schedule). A bulk CSV update keeps tickets accurate right before doors open.
* A gym chain exports membership status every night (active, frozen, expired). The next day, members see the correct status at check-in.
* A support team fixes a wrong identifier at scale by uploading a one-time correction file.

</details>

<div data-with-frame="true"><figure><img src="https://3566051324-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FWLc8AHXW4tdrAXUBfrYF%2Fuploads%2Fgit-blob-d166db36c4e1ffc5a5f462769bb62a772eee361c%2Fupdate_pass_using_flat_files_1.png?alt=media" alt="Diagram showing a CSV file uploaded to SFTP, then processed by The Wallet Crew to update passes."><figcaption><p>Flat file imports let you update many passes in one operation.</p></figcaption></figure></div>

## Process overview

1. Upload your CSV file to the provided SFTP server.
2. The Wallet Crew automatically detects new files and processes them immediately.
3. Once processed, the file is moved to `.processed/` or `.error/`.
4. Each row is interpreted as an update instruction for a single pass.
5. Passes are queued for update and processed asynchronously.

## File format

Uploaded files must be valid [RFC 4180](https://www.ietf.org/rfc/rfc4180.txt){target="\_blank"} compliant CSV files.

* **Separators**: Either `,` or `;`
* **Header row**: Mandatory. Must contain column names.
* **Encoding**: UTF-8 recommended
* **Parser**: The Wallet Crew uses [CsvHelper](https://joshclose.github.io/CsvHelper/){target="\_blank"} with default options

Each data row targets a single pass and can include multiple updates to its fields, additionalData, or metadata.

## File name

There are no strict file name requirements. However, for better organization and traceability, we recommend the following naming convention:

```
<source>-passes-<YYYYMMDD>-<HHMMSS>.csv
```

Examples:

```
crm-passes-20250801-083000.csv 
loyalty-passes-20250731-235959.csv
```

Notes:

* The file extension must be `.csv`.
* Avoid using special characters (spaces, #, %, &, etc.) in file names
* If you are using a script or integration to generate files, adopting a timestamp-based naming pattern helps avoid duplication and makes debugging easier.
* If the file is large, The Wallet Crew briefly monitors its size to ensure it is fully written before processing.

## Column reference

### Pass identifier columns

To select the target pass, use one of the following columns:

* `id`: Internal The Wallet Crew pass identifier
* `id.<externalId>`: External identifier name (as configured in your tenant)\
  Example: `id.y2.customerId`

At least one identifier column must be present in each row.

### Additional data columns

To update additional data on the pass, use the following syntax:

* `additionalData.<key>`: Updates the key in the pass’s `additionalData` dictionary\
  Example: `additionalData.notification_content`

Multiple additionalData keys can be updated in one row.

### Pass template

To switch the pass to a different template, include:

* `passType`: The new template name (as defined in The Wallet Crew)

If omitted or empty, the pass will retain its current template.

### Metadata refresh

To trigger a recomputation of internal metadata (e.g., barcodes, expiration, display fields):

* `updateMetadata`: Set to `true` to trigger metadata update

### External identifiers

To update an external identifier, use the following syntax:

* `identifiers.<idName>`: Updates the identifier named `idName`\
  Example: `identifiers.y2.customerId`

## SFTP access

Upload your CSV files to the SFTP endpoint for your environment:

* **QA**: `triglav-qa.walletcrew.net` (port `22`)
* **Production**: `triglav.walletcrew.net` (port `22`)

Contact The Wallet Crew Support to request your SFTP credentials.

## Examples

### Example 1: Update Notification and Offer URL

```csv
id;additionalData.notification_content;additionalData.offer_url
Gk440DNzvfZcDmlA;Merry Christmas Alice!;https://acme.com/xmas1
HKlhrhrEXx2ZASYs;Merry Christmas Bob!;https://acme.com/xmas1
```

### Example 2: Update by External ID with Loyalty Info

```csv
id.y2.customerId;additionalData.notification_content
00100123;Enjoy 30% off on your next purchase
04503295;Thanks for your purchase! Only 10 points left to redeem a €10 voucher
02319202;Thanks for your purchase! Only 120 points left to redeem a €10 voucher
```

### Example 3: Switch Pass Template and Force Metadata Refresh

```csv
id;passType;updateMetadata
gH67xKlPzLZ99xa2;vip_template;true
dAk21jvUZYx39q77;standard_template;true
```

### Example 4: Update external id y2.customerId and force metadata refresh

```csv
id.y2.customerId;identifiers.y2.customerId;passType;updateMetadata
04503295;1010013295;;true
04503296;1010013296;;true
```

## Extensibility and custom mapping

The Wallet Crew supports custom file transformations using import scripts.

This allows you to:

* Adapt your existing export format (e.g., from a CRM or POS)
* Map legacy column names to The Wallet Crew-compatible keys
* Inject dynamic values (e.g., current date, calculated points)
* Enrich data with lookups from external sources

To customize the import process, you can create an `import.custom.js` file in the `scripts/` folder in Advanced configuration.

If you have the following CSV file:

```csv
id.y2.customerId,neo_notification_content,neo_offer_title,neo_offer_body
abc12345,"Hey Arthur !","Your 20% offer","20% Off for your next purchase"
```

You can use the following script:

```js
/**
 * Transforms a CSV row into an UpdatePassInformation-like object.
 *
 * @param {Object<string, string>} row - The CSV row, as a dictionary of column names to values.
 * @returns {Object} Transformed row information.
 * @returns {Object<string, string>} return.Identifiers - Identifiers for this row (e.g., customer ID).
 * @returns {string|null} return.PassType - The type of pass, or null if not present.
 * @returns {Object<string, string>} return.AdditionalData - Optional additional data from prefixed columns.
 */
function transform(row){
  const identifiers = {
      "id.y2.customerId" : row["id.y2.customerId"]
  };

  const passType = row.passType || null; 

  const properties = [
    "notification_content",
    "offer_title", 
    "offer_body"
   ];

  let additionalData = {};
  for(const property of properties){
    if(row["neo_" + property] !== undefined){
      additionalData[property] = row["neo_" + property].toString(); 
    }
  }

  return {
    Identifiers : identifiers, 
    PassType: passType,
    AdditionalData : additionalData
  }
}

/**
 * Optional method to return a custom throughput for a given import file.
 *
 * @param {string} fileName - The import file name.
 * @returns {number|null} Throughput override, or null to use default.
 */
function getThroughput(fileName) {
  // Example: return null to use default
  return null;
}

export default function(context) {
  context.register('runtime.import.updatePasses.rowTransformer', {
    Transform: transform, 
    GetThroughput: getThroughput
  });
}
```

Contact our team if you need help implementing custom mapping logic for your imports.

## Tips and best practices

* Ensure identifiers are accurate to avoid skipped rows.
* Always test your file format in QA before uploading to production.
* Use consistent encoding (UTF-8) to avoid parsing issues with special characters.

## FAQ

<details>

<summary><strong>Do you create new passes, or only update existing ones?</strong></summary>

This flow updates existing passes. Each row must target a pass using `id` or an external identifier column like `id.y2.customerId`.

</details>

<details>

<summary><strong>What happens if a row has both <code>id</code> and <code>id.&#x3C;externalId></code>?</strong></summary>

Use one identifier per row if you can. If you include several, make sure they all point to the same pass. This avoids ambiguity and makes troubleshooting easier.

</details>

<details>

<summary><strong>Can I update multiple <code>additionalData</code> keys in one row?</strong></summary>

Yes. Add one column per key, using `additionalData.<key>`. The row will update all provided keys in one pass update.

</details>
