# Custom fields

Define your own labels for a company's invoices (store, site, cost centre), fill them in, and filter and export by them.

A custom field is a label that belongs to one company: *Store*, *Site*, *Cost centre*, *Project*. You define it once, and every invoice of that company can carry a value for it. People see it on screen and, if you choose, on the PDF; you can filter the invoice list and the spreadsheet export by it.

BeeL. gives a field no meaning of its own. It never affects taxes, totals or numbering.

| What | Operation |
|---|---|
| Create or update a field | [`PUT …/custom-fields/{key}`](/custom-fields/upsertCompanyCustomField) |
| List the fields, with their options | [`GET …/custom-fields`](/custom-fields/listCompanyCustomFields) |
| Get one field | [`GET …/custom-fields/{key}`](/custom-fields/getCompanyCustomField) |
| List the options of a field | [`GET …/custom-fields/{key}/options`](/custom-fields/listCompanyCustomFieldOptions) |
| Add an option in advance | [`POST …/custom-fields/{key}/options`](/custom-fields/createCompanyCustomFieldOption) |
| Rename, archive or reactivate an option | [`PATCH …/custom-fields/{key}/options/{option_id}`](/custom-fields/patchCompanyCustomFieldOption) |

All paths hang off `/v1/companies/{company_id}`. Reading needs the `custom-fields:read` scope and writing needs `custom-fields:write`. Test and Live keep separate catalogues: a field created with a Test key does not exist in Live.

## Metadata or custom fields [#metadata-or-custom-fields]

Both attach extra data to an invoice. Pick by who reads it:

| | `metadata` | `custom_fields` |
|---|---|---|
| For | Technical identifiers of your integration: order id, tenant, source system | What a person sees, filters or exports: store, site, cost centre |
| Visible to people | No | Yes, on screen and optionally on the PDF |
| Defined beforehand | No, free key/value pairs | Yes, a catalogue per company |
| Filter | Exact match on any key, see [Filtering by metadata](/guides/filtering-by-metadata) | Exact match on the text saved on the invoice, which the options keep consistent |

If a person will ever read the value, use a custom field. If only your code does, use `metadata`.

## Create a field [#create-a-field]

The `key` is the path and it names the field for good: lowercase letters, digits and `_`, up to 64 characters. `PUT` creates the field or updates it, so you can run it on every deployment to seed the catalogue. It answers `201` when it creates the field and `200` when it already existed.

```bash
curl -X PUT "https://app.beel.es/api/v1/companies/{company_id}/custom-fields/store" \
  -H "Authorization: Bearer $BEEL_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{ "label": "Store", "show_on_pdf": true }'
```

`label` and `show_on_pdf` are always replaced. `suggest_values` and `is_archived` keep their current value when you omit them, so repeating the request never undoes a change someone made in the app. A new field starts active and with `suggest_values: true`.

## Use it on an invoice [#use-it-on-an-invoice]

Send `custom_fields` as a list of `key` and `value`, at most one value per `key`:

```json
{
  "type": "STANDARD",
  "recipient": { "customer_id": "4f244735-980b-4d9c-80e8-6331fa0b1958" },
  "lines": [
    { "description": "Consulting", "quantity": 1, "unit_price": 500,
      "main_tax": { "type": "IVA", "percentage": 21, "regime_key": "01" } }
  ],
  "custom_fields": [
    { "key": "store", "value": "Gran Vía 12" }
  ]
}
```

The invoice answers with the same list, and each entry also carries the `label` the field had when the invoice was saved:

```json
"custom_fields": [
  { "key": "store", "label": "Store", "value": "Gran Vía 12" }
]
```

The invoice keeps a copy of the label and the text. Renaming or archiving the field, or its options, later does not change any invoice already saved, and neither does it change the PDF when it is regenerated. An invoice with no custom fields answers `[]`.

To change them, send `custom_fields` in [`PATCH …/invoices/{invoice_id}`](/invoices/patchCompanyInvoice): the list replaces the current one as a whole, omitting it keeps the current ones, and `null` or `[]` removes them all. Only a draft, a scheduled invoice or an active proforma can change them.

## Options fill themselves in [#options]

With `suggest_values: true`, the values already used become the options the app offers next time, per customer. When you save an invoice for a registered customer (`recipient.customer_id`) with a value that is new, BeeL. adds it as an option of that customer. With recipient data sent inline there is no customer to attach it to, so the text is stored and no option is created.

Before adding it, BeeL. checks whether an equivalent option already exists, among those of that customer and the common ones. Two values are equivalent when they match ignoring case, accents and repeated spaces, and `ñ` counts as `n`:

| You send | Existing option | The invoice stores |
|---|---|---|
| `gran via 12` | `Gran Vía 12` | `Gran Vía 12` |
| `GRAN  VÍA 12` | `Gran Vía 12` | `Gran Vía 12` |
| `Peña` | `Pena` | `Pena` |
| `Gran Vía 14` | none equivalent | `Gran Vía 14`, and a new option |

So a typo in case or accents does not split your filter in two. An archived option that matches is reused with its text and stays archived.

To seed a catalogue before the first invoice, add options with [`POST …/custom-fields/{key}/options`](/custom-fields/createCompanyCustomFieldOption), for one customer or, without `customer_id`, common to all of them. It answers `201` when it creates the option and `200` with the existing one when an equivalent is already there.

### Values that change on every invoice [#suggest-values]

For something unique per invoice, such as an order number, send `"suggest_values": false`. The invoice then stores the text as sent, without leading and trailing spaces, it is not compared with any option and it creates none. Turning the setting off deletes no option, and turning it on again offers them again.

### Read the options for a customer [#read-options]

`GET …/custom-fields` returns each field with its common options. Add `customer_id` to get those plus the ones of that customer, which is what an invoice for that customer can offer. A `customer_id` that is not a customer of the company is not an error: only the common options come back. To page through every option of a field, use `GET …/custom-fields/{key}/options`.

Renaming an option changes only what is offered from then on. If another option in the same scope is equivalent to the new text, [the rename](/custom-fields/patchCompanyCustomFieldOption) answers `409` [`CUSTOM_FIELD_OPTION_DUPLICATE`](/errors/CUSTOM_FIELD_OPTION_DUPLICATE).

## Archive, never delete [#archive]

Fields and options are never deleted, and a `key` never changes. To stop using a field, archive it:

```bash
curl -X PUT "https://app.beel.es/api/v1/companies/{company_id}/custom-fields/store" \
  -H "Authorization: Bearer $BEEL_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{ "label": "Store", "show_on_pdf": true, "is_archived": true }'
```

An archived field cannot be added to new invoices, and invoices that already carry it keep it. Archived fields and options are left out of the listings unless you send `include_archived=true`.

## Filter the list and export [#filter-and-export]

[`GET …/invoices`](/invoices/listCompanyInvoices) takes `custom_fields[<key>]=<value>`, the same bracket style as `metadata`. Several keys combine with AND, and also with the rest of the filters, such as `customer_id`. You can send up to 20 keys.

```bash
curl -g "https://app.beel.es/api/v1/companies/{company_id}/invoices?customer_id={customer_id}&custom_fields[store]=Gran%20V%C3%ADa%2012" \
  -H "Authorization: Bearer $BEEL_API_KEY"
```

The value is compared with the text saved on the invoice, exactly, case and accents included. Use the text the invoice carries, which is the one the option has. After you rename an option, invoices saved earlier are found by the old text. A key the company does not have matches no invoice.

The spreadsheet export takes the same filter inside `filters`:

```json
{
  "filters": {
    "customer_id": "550e8400-e29b-41d4-a716-446655440010",
    "custom_fields": { "store": "Gran Vía 12" }
  }
}
```

Send it to [`POST …/invoices/exports`](/invoices/createCompanyInvoiceExport). It behaves like the list filter. More than 20 keys is rejected with `422` [`CUSTOM_FIELD_FILTER_TOO_MANY_KEYS`](/errors/CUSTOM_FIELD_FILTER_TOO_MANY_KEYS).

## Recurring templates, corrective invoices and other copies [#inheritance]

- **Recurring invoices.** A template carries `custom_fields` and copies them to every invoice it generates. If you create a template from an invoice, it takes that invoice's fields. A field archived later is left out of the invoices generated from then on, and the template keeps generating.
- **Corrective invoices.** A corrective carries the fields of the original, each with the label the field has now. A field archived since is left out.
- **Proforma to invoice.** The new invoice copies the fields of the proforma, with the same rule.

## On the PDF [#pdf]

With `show_on_pdf: true`, the PDF prints the field in a block of its own under the notes, with its label and value. An invoice keeps the setting it was saved with, so changing `show_on_pdf` affects invoices saved from then on.

## Not a tax mention [#not-fiscal]

<Callout type="warn">
  Custom fields are informational. They are not part of the VeriFactu record sent to the AEAT and do not enter its hash. The mentions the regulations require (an exemption, the reverse charge) stay in their own fields of the invoice and cannot be written as a custom field.
</Callout>

## Gotchas [#gotchas]

| Case | Error | Status | Why |
|---|---|---|---|
| A `key` in `custom_fields` is not a field of this company in this environment. Create it with `PUT …/custom-fields/{key}` or fix the key; nothing is created | [`CUSTOM_FIELD_UNKNOWN`](/errors/CUSTOM_FIELD_UNKNOWN) | `422` | A `key` in `custom_fields` is not a custom field of this company in this environment. |
| A `key` in `custom_fields` names an archived field. Reactivate it with `"is_archived": false`, or send the list without it; nothing changes | [`CUSTOM_FIELD_ARCHIVED`](/errors/CUSTOM_FIELD_ARCHIVED) | `409` | A `key` in `custom_fields` names a field the company has archived. |

- The same rule applies to the create and the update of invoices and recurring templates, even when a draft already carries a field that was archived afterwards.
- Changing only the recipient of an invoice, with `custom_fields` omitted, carries the saved fields to the new customer. A field archived since is left out with no error.
- A value is text of 1 to 255 characters once leading and trailing spaces are removed; an empty one answers `422`.
- A repeated `key` in the same list answers `422` [`VALIDATION_ERROR`](/errors/VALIDATION_ERROR).

## Related

<Related>

- [Filtering by metadata](/guides/filtering-by-metadata) — identifiers for your own system, invisible to people
- [Recurring invoices](/guides/recurring-invoices) — templates that copy these fields to each invoice
- [Bulk operations and exports](/guides/bulk-operations-and-exports) — the spreadsheet export and its filters
- [Create or update a custom field](/custom-fields/upsertCompanyCustomField)

</Related>

---

Full OpenAPI spec: https://docs.beel.es/api/openapi