pforte

Importing users from a CSV file

The back office usually exports its people as a spreadsheet. pforte import reads that file and brings pforte in line with it, through the admin API. It is safe to run again: rows that change nothing are reported as unchanged.

export PFORTE_URL=https://id.example.com
export PFORTE_CLIENT_ID=importer          # a machine client with the "directory" scope
export PFORTE_CLIENT_SECRET=…             # or PFORTE_CLIENT_SECRET_FILE=/path/to/file

pforte import users.csv --dry-run         # look first, writes nothing
pforte import users.csv                   # then do it

In this repository run it as pnpm pforte import users.csv --dry-run. The seed registers demo-import (secret dev-secret) for that.

The file

The first row names the columns, in any order. The delimiter (, or ;) is detected, so a file saved by a German Excel works. Save it as "CSV UTF-8"; for an older file use --encoding windows-1252.

Column Required Notes
email yes Identifies the user. Must be unique in the file.
first_name, last_name yes
phone no Spaces, dashes and brackets are removed; 0049… becomes +49…. A blank cell clears the phone, a missing column leaves it alone.
language no Two or three letters, e.g. de.
any claim type no The key of a claim type, e.g. partner_no. Several values in one cell are separated by ; (--value-separator). A blank cell removes that claim type from the user.

A column that is neither of these stops the import and suggests the closest name ("did you mean partner_no?"). --ignore-unknown-columns skips such columns with a warning instead.

An example that works against the seeded database is examples/users.csv: three new people (one with two partner numbers), an update of a seeded user, and two rows that are rejected, so that you can see what that looks like. Try it with --dry-run, or on the Import page of the administration.

Rejected rows

A row with a problem (bad e-mail, a value that does not match the claim type's pattern, a second value for a single-valued claim, a duplicate e-mail) is not imported; the others are. The summary lists them with line number and reason, and --rejected rejected.csv writes them in the layout of your file plus line and reason columns, so you can fix them and import that file again. --report report.json writes everything, row by row.

Users who are not in the file

By default they are left alone. --missing=lock locks them, --missing=delete deletes them (and needs --allow-delete, since that cannot be undone). Administrators are never touched. Rows that were rejected still count as present, so a typo does not lock the person. As protection against a wrong or truncated file the import refuses to touch more than 20 % of the users (--max-missing-percent, override with --force), and a file without a single valid row is refused.

Exit codes

Code Meaning
0 Everything was imported.
2 The import ran, but some rows were rejected or failed.
1 The import could not run: no file, bad option, missing settings, unknown column, refused credentials, API unreachable.

Speed

Rows are sent in parallel (--concurrency, default 8). 3,000 rows took 2 s against a local server; a repeat run with nothing to change took 1.5 s.

Edit this page on GitHub