# Import members from a spreadsheet

> How to bring a list of people from any .xlsx or .csv spreadsheet into the community - origin, file, columns, groups and tags, consent, summary and report -, what each person gets by email and how they decline the invite.

The **Import spreadsheet** tab of the **Invite Members** window brings the people in a spreadsheet into the community: the attendee list of an event, a member base, the students of a class. Any spreadsheet works: you say where each column goes, choose role, groups and tags for everyone, check a summary and only then import. Each new person gets an invitation by email.

> [!WARNING]
> Only import people who **agreed** to receive emails and to join the community. Bought lists, giveaways and data collected by bots are forbidden. Read the [Invitation and member import policy](/membros/politica-de-convites) before importing.

## What it's for

| Community | Spreadsheet |
|---|---|
| Event | The attendees who ticked *"I want to join the community"*, with the ticket type column becoming a tag |
| Association | The member base, with company and job title becoming custom fields |
| School | The semester's enrolled students, all in the class group |
| Coworking | The residents, with the *Floor* column going to Groups |

## How it works

### Who can import

The **owner** and **admins**. The import only gives the **Member** or **Moderator** role: anyone who needs to join as admin or finance is invited from the **Type e-mails** tab. See [Invite members](/membros/convidar-membros).

### The file

- **.xlsx** or **.csv**. The old `.xls` and `.xlsm` (with macros) are refused, even renamed.
- Up to **5 MB**, **5,000 data rows** and **100 columns**.
- Formulas count by their calculated value; merged cells, by the value of the first one.
- In a CSV, Memberfy works out the **encoding** on its own (correct accents even in a file saved by an old Excel) and the **delimiter** (comma, semicolon, tab or pipe). You can change both.

### What happens to each row

| Case | What happens |
|---|---|
| Not a member of this community yet (even with an account in another) | Gets an account in this community, with the name and phone from the spreadsheet (without a name, the part of the email before the @), and joins with the role, the groups (including the [default group](/membros/grupos-e-organograma)), the tags, the job title, the bio and the fields. Gets the invitation with **Set my password**. Accounts are per community: another community's password doesn't open this one |
| **Already a member** | Role and name don't change (it only fixes a name that was still the part of the email before the @). **Gains** the groups and tags (nothing is removed) and only what is **empty** is filled in: custom fields, job title and bio. The spreadsheet's **phone** replaces the one on the profile without being verified (with the consent declaration, it gets SMS and WhatsApp right away), except when the person has already verified another number (that one stays) or when an admin imports the owner. Gets no email. Counts as *updated* |
| Email repeated in the spreadsheet | The first row counts; the others are skipped as *duplicate in the spreadsheet* |
| No email or an invalid email | Skipped with the reason, without stopping the rest |
| No consent | Skipped (when there's a consent column) |
| Asked not to take part in this community | Skipped; no email goes out |
| A value that doesn't fit the field | Only that field stays empty; the person joins and the report warns you |
| Job title or bio over the limit | Comes in cut at the limit (100 characters for the job title, 200 for the bio); the row shows the *value cut* warning |

### The invitation email

Every person **new** to the community gets an email on the community's behalf, with the **list origin** (*"You got this invitation because your details are in: Attendees of event X in 2026"*) and, in the footer, the link **Thanks for the invitation, but I do not want to join**. The invitation brings **Set my password** (the link is valid for 7 days and works once), including for someone who already belongs to another community. Existing members get nothing.

Import emails **don't count** toward the [campaign quota](/notificacoes/campanhas-de-email#sending-quota).

### In the background

When you click **Import**, the import goes into a queue and runs on its own: you can close the window. Invitations go out at the same pace as campaigns, about **5 per second**, so a list of 3,000 people takes around 10 minutes to be fully delivered. At the end, whoever imported gets an in-app notice and an email with the summary.

A community reads **one spreadsheet at a time**. If someone else on the team is importing at the same moment, the screen says *"Another spreadsheet is being read right now. Try again in a moment."*

### What is kept

The import records who imported, when, the file name, the origin and the consent used (the column and the values, or the declaration). A **copy of the original file** is kept in private storage, with no deletion date: the owner and admins download it from the history (**Previous imports › View › Download the original file**). On the record of whoever joined, the team reads *"Origin: spreadsheet import on …"*.

## Step by step

*Role: owner or admin.*

1. **Administration › Members**, **Invite Members** button, **Import spreadsheet** tab.
2. At the top of the window, choose the **Role** (Member or Moderator), the **Groups** and the **Tags** for everyone. New groups and tags are created right there by typing the name. These three fields are the same as on the **Type e-mails** tab: what you choose on one applies to the other.
3. **1. Source.** Write the **List origin** (required, up to 150 characters, no links), for example *Attendees of event X in 2026*. It goes in each invitee's email. Choose the file (drag it or click) and click **Next**.
4. **2. File.** Check how it was read: the **Sheet** (in an .xlsx with several), the **Header row** (*"Header on row 8."*, when the spreadsheet has a title above it) and, in a CSV, the **Delimiter** and the **Encoding**. Changing any of them rereads the file.
5. **3. Columns.** Each column shows with its first 3 rows and the **Goes to** selector. Columns **Recognized automatically** (email, first name, last name, phone, job title, bio, existing fields) come first; the others are left on **Ignore**, collapsed under *"28 ignored columns — show"*. The destinations:

   | Goes to | Rule |
   |---|---|
   | **E-mail** | Required, one column only. Without it, *"Choose the e-mail column to continue."* |
   | **First name** and **Last name** | Together they make the full name. A full-name column also works |
   | **Phone** | Adjusted to the international format, with the community's country as the default; an unreadable one stays empty |
   | **Job title** | The profile job title, up to 100 characters. Suggested for headers like *Job title*, *Cargo* or *Função* |
   | **Bio** | The profile bio, up to 200 characters. Suggested for *Bio* or *Biografia* |
   | **Tags** | Each value becomes a tag (separate several with `,` `;` or `\|`). E.g.: *Ticket type* |
   | **Groups** | Each value puts the person in the group with that name; a missing one **becomes a new group** |
   | **Groups: valid until** | One date per row (*12/31/2026*): the person stays in that row's groups until the end of that day and then leaves by themselves (see [Valid until](/membros/grupos-e-organograma#valid-until)). Empty, invalid or past, the groups have no end date, and the row warns |
   | **Badges** | The badges the person already had, by name from the community's [catalog](/membros/engajamento#badges), separated by `;` (*Founder; Mentor*). See [Badges and initial points](#badges-and-initial-points) |
   | **Initial points** | The points the person already had, from 1 to 100,000. See [Badges and initial points](#badges-and-initial-points) |
   | **Consent** | The column that says who agreed. Mark which values **count as yes** (*Yes*, *x*, *agreed*…) |
   | **SMS consent** | The column that says who agreed to get service notices by SMS. Tick which values say "yes" (see [SMS consent](#sms-consent)) |
   | **Field: …** | An existing [custom field](/membros/campos-customizados). For a **Multiple choice**, separate the options in the same cell with `;` or `,` (*Linux; Cloud*) |
   | **+ New custom field** | Creates the field with the column's name (editable) and the type suggested by the content; a column with lists separated by `;` may suggest **Multiple choice**, created with the distinct values as options (2 to 50). It is created **optional** |
   | **Ignore** | The column isn't imported |

   Consent, SMS consent, Tags and Groups are never suggested: you choose them. Columns with the same name show separately (*Phone (col. 24)*, *Phone (col. 30)*), and only one can go to each of the person's details.
6. **Consent.** With a consent column, only rows with the marked values get in. Without one, tick the declaration *"I declare that everyone on this list agreed to receive e-mails and to join this community."* Without one of the two, you can't import.
   With a column in **SMS consent**, also tick the SMS attestation (see below).
   **Values that aren't options yet.** For a column going to a **Choice** or **Multiple choice** field, the summary flags the spreadsheet values the field doesn't have yet (*"Interests: 2 value(s) in the sheet are not options of the field yet."*). **Add as options** adds those values to the field; without it, the cell comes in with a warning and without the value.
7. **4. Summary.** Check before saving: **Rows in the spreadsheet**, **New people**, **Already members**, **Will be skipped** (with the reason), **Skipped because they asked not to join**, **Invite e-mails to be sent** and the groups, tags and fields that **will be created**, by name. Clicking **Import** confirms all of this; there's no other question.
8. **5. Result.** Progress shows in the window. At the end: **Imported**, **Updated**, **Skipped** and **Invites sent**, the list of **Skipped rows** with the reason, the **Rows imported with a warning** and the **Download errors CSV** button (the original rows with a reason column, to fix and import again).

**Previous imports**, on the same tab, shows the history: each import with date, counts, status and the original file.

### SMS consent

For the people in the spreadsheet to get [SMS notices](/notificacoes/avisos-por-sms-e-whatsapp), send the column that records that agreement to **SMS consent** and:

1. Under **Which values say "yes" to SMS?**, tick the values that count as yes (*yes*, *x*, *sim*…). Empty or another value: the person joins but does not get SMS.
2. Tick *"I attest that this community collected SMS consent from the people marked "yes"."* The attestation is recorded with today's date and who imported. It is what allows SMS notices to these people: without it, no one in this spreadsheet gets SMS.

The summary shows the rule used (*SMS consent from the "Accepts SMS" column (yes: Yes, x).*). Each marked person gets the SMS service consent, **whether new or already a member**: the declaration is the community's, and so is the responsibility for it. New people get the origin **import**. Some rules don't change:

- Someone who **left the channel** or **withdrew consent** is never re-enrolled: the row comes in with the consent-withdrawn warning, and nothing is recorded.
- The phone on a row with the declaration **can receive right away**: SMS (and WhatsApp, through the API and the assistant) without the code verification, while it is the profile's current phone; a number changed later does not inherit the declaration, and one saved later without a declaration loses it too. It is **not verified**: verification codes still require the person to confirm it themselves. See [Consent declared by the team](/notificacoes/avisos-por-sms-e-whatsapp#consent-declared-by-the-team).
- An admin never declares for the owner: the owner's row records no consent.
- When the person has already verified **another** number, it stays, and the row says so.

On screen, the import has no WhatsApp consent column: through the [API](#in-the-api) or the [assistant](/mcp/ferramentas-membros#preview-member-import), the `whatsappConsent` column follows the same rule as SMS. For existing members, the team can also record consent without a spreadsheet: see [Consent declared by the team](/notificacoes/avisos-por-sms-e-whatsapp#consent-declared-by-the-team). This column is separate from the **Consent** column, which decides who joins the community.

### Badges and initial points

In the columns step, pick **Badges** or **Initial points** in the destination. In the summary, each name outside the catalog has **Create badge** and **Ignore** (*"Create or ignore each one to be able to import."*); it also shows the rows with invalid points, who already got points from an import, the gamification-off warning and **The level people will land on**.

For anyone coming from another platform who wants to bring each person's history. The full walkthrough is in [Migrating from another platform](/primeiros-passos/migrar-de-outra-plataforma).

- **Badges.** One column, with the names separated by `;` (a comma can be part of a name). Only what is in **this** community's catalog counts, ignoring case and accents. A name that is not in the catalog **blocks the import** until you create the badge with that name or mark it to be **ignored**. Whoever already holds the badge keeps it.
- **Initial points.** One column, with a whole number from 1 to 100,000. `1.500`, `1,500` and `250.0` are accepted; empty or 0 does nothing. A value with decimals (`12.5`) or that is not a number is invalid: the person comes in **without the points**, and the row shows a warning. Each person gets import points **only once**, across every import: in a second spreadsheet, whoever already got them stays as they are. With [gamification](/membros/gamificacao) off, the points are ignored.
- **The summary** shows the names outside the catalog, the invalid point values (with the row), who already got points before and **which level** people will land on. People who arrive through the import have no weekly activity yet, so a level that requires activity only comes later.
- **Silently.** Imported badges and points send no e-mail or bell notice and fire no workflows (**Received a badge**, **Moved up a level**, **Reached N points**). The badge-received [webhook](/api/eventos-de-webhook) does go out, with the import origin. The e-mail invite to new people still goes out as usual.
- They apply to new people and to existing members.

## Decline the invite

Someone who was imported and doesn't want to take part has two ways out:

- **From the email:** the link **Thanks for the invitation, but I do not want to join** opens a community page, with no sign-in, with **Confirm: I don't want to join**.
- **In the community:** in **My Account**, the same sentence shows discreetly, with the explanation *"You were added to this community by a list import."*

On confirming, the person leaves the community and goes on the **decline list**: the community can no longer import or invite them. Their Memberfy account stays. They only come back by joining on their own, through open sign-up.

- The email link is valid for **30 days** and stops working when the person **accepts the invite** (sets the password and signs in) or when their **role changes**. After that, to leave, they talk to whoever runs the community.
- Declining **doesn't apply to team members** (moderator, admin, finance) or to anyone with an active or pending **subscription** in the community: the person cancels the subscription first.
- The decline list also applies to the **email invite** and to **New Member**: whoever declined comes back as *"Asked not to take part in this community"*, and no email goes out.

A single decline isn't a violation; many declines from the same list lead Memberfy to review the community. See the [Invitation policy](/membros/politica-de-convites#how-an-invited-person-declines-or-reports).

## Examples

**An event with 220 attendees.** The spreadsheet has a title in the first rows and the header on row 8. The organizers check the header row, send the *Agreed to receive news* column to **Consent** with *Yes* counting as yes, *Ticket type* to **Tags** and *Company* to **+ New custom field**. The summary shows 97 new people, 109 without consent, 12 duplicates and 2 without email. They import; 97 invitations go out.

**An association bringing in its base again.** Half are already members. That half gets the *Members 2026* group and fills in the *Company* that was empty, with no email; only the new people are invited.

## Common errors and how to fix them

| Message | What to do |
|---|---|
| *Upload an .xlsx or .csv file.* | Save the spreadsheet as .xlsx or .csv |
| *The file is over 5 MB.* / *The spreadsheet has more than 5,000 data rows.* | Split it into parts and import one at a time |
| *No clear header was found, so row 1 was used…* | Pick the right **Header row** in the File step |
| Garbled accents (*JoÃ£o*) | Change the **Encoding** in the File step |
| *Choose the e-mail column to continue.* | Send a column to **E-mail** |
| *Only one column can go to Phone.* | Leave the other on **Ignore** or send it to a custom field |
| *Mark which values of the consent column count as yes.* | Mark the values under **Count as yes** |
| *Tell where the list comes from.* | Fill in the origin in step 1 |
| *Check the consent declaration to import.* | Without a consent column, the declaration is required |
| *Another spreadsheet is being read right now. Try again in a moment.* | Wait a few seconds and click again |
| *Spreadsheet imports only accept member or moderator.* | For other roles, use **Type e-mails** |
| *Interrupted by the community's suspension* | Memberfy suspended people joining; what had already been processed stays. See [Suspension of joining and sales](/membros/politica-de-convites#suspension-of-joining-and-sales) |

## Frequently asked questions

**Can I import without sending an email?**
No. Every new person gets the invitation, with the origin and the decline link: that's what lets them know where their data came from and say no.

**Can I undo an import?**
There's no undo button. To remove someone, use [Manage members](/membros/gerenciar-membros).

**Can I import someone who declined?**
No. Someone who declined only comes back by joining on their own.

**The groups column has a name written differently from the group.**
The match ignores case and accents (*engineering* finds *Engineering*). A name that matches no group creates a new one, and the summary shows which before you import.

**Does the consent column cover SMS?**
No. **Consent** decides who joins and gets the e-mail invite; SMS needs the **SMS consent** column and the attestation.

**Can I import through Claude or ChatGPT?**
Yes: attach the spreadsheet to the conversation and ask. See the recipe [Import a spreadsheet through the assistant](/mcp/receitas#61-import-a-spreadsheet-through-the-assistant).

## In the API

`POST /api/members/imports` (the file, as `multipart`), `POST /api/members/imports/{id}/preview`, `POST /api/members/imports/{id}/start`, `GET /api/members/imports`, `GET /api/members/imports/{id}`, `GET /api/members/imports/{id}/errors.csv` and `GET /api/members/imports/{id}/file`. Declining from the email link is [`GET /api/invite-opt-out/{token}`](/api/referencia/member-imports/get-invite-opt-out-by-token) (the page, no sign-in) and [`POST /api/invite-opt-out/{token}`](/api/referencia/member-imports/post-invite-opt-out-by-token) (the confirmation); signed in, `POST /api/members/me/decline`. SMS consent is the `smsConsent` target in the mapping, with the options `smsConsentValues` and `smsConsentAttestation: true`; WhatsApp consent is `whatsappConsent`, with `whatsappConsentValues` and `whatsappConsentAttestation: true`. The row warnings are `CONSENT_WITHDRAWN` (consent withdrawn, nothing recorded) and `PHONE_KEPT_VERIFIED` (another verified number was kept). See [Member Imports](/api/referencia/member-imports) in the API reference. The `badges` and `points` targets (one of each), with `ignoreNames` on the badges column for the names to ignore; without it, a name outside the catalog makes `start` return `400` `memberImports.errors.badgesUnknown`. The preview carries `summary.badges` (`unknown`, `ignored`, `grants`) and `summary.points` (`rows`, `invalidCount`, `invalid`, `alreadyImported`, `gamificationOff` and `levels`). The `groupsExpireAt` target (one column) gives the row's groups their end date; an invalid or past date becomes the `INVALID_VALUE` warning, and the groups have no end date. The row warnings are `INVALID_POINTS`, `POINTS_ALREADY_IMPORTED` and `UNKNOWN_BADGE` (the badge left the catalog during the import).

## Related

- [Invitation and member import policy](/membros/politica-de-convites)
- [Invite members](/membros/convidar-membros)
- [Custom fields](/membros/campos-customizados)
- [Groups and org chart](/membros/grupos-e-organograma)
- [Tags](/membros/tags)
- [SMS and WhatsApp notices](/notificacoes/avisos-por-sms-e-whatsapp)
- [Migrating from another platform](/primeiros-passos/migrar-de-outra-plataforma)
