> ## Documentation Index
> Fetch the complete documentation index at: https://docs.terminus.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Part 3: Fields basics

> Add three user-input fields (campaign_name, launch_date, destination_url) and learn field types, required and unique, defaults, and text transforms.

<Info>
  **What you'll learn:** how fields work: types, required and unique, default values, and text transforms.

  **What you'll build:** three user-input fields on the `Marketing` model: `campaign_name` (Text), `launch_date` (Date), `destination_url` (URL).

  **Prerequisites:** [Part 2: Create your governance model](/tutorial/02-governance-model).
</Info>

## Why this matters

Fields are the atoms of Terminus Hub. Everything else (picklists, taxonomies, computed URLs) either produces a field, references a field, or wraps a field. Getting the first three right sets the shape of every campaign record you create: campaigns need a name, a launch date, and a destination URL. The settings you choose now (required, unique, lowercased, underscored) are what make later steps like `utm_campaign` assembly reliable.

## Concepts first

### Fields are defined once, on the model

A field is a definition on the governance model. Any taxonomy in the same model can reference it. You will see this pay off in Part 8, when the same `campaign_name` field appears on all three taxonomies without being redefined. This part only creates the definitions. Nothing is connected to a taxonomy yet.

### User-input versus computed fields

Field types split into two families. **User-input** fields (Text, URL, Date, Dropdown) accept a value a person types or picks. **Computed** fields (Concatenation, Tagged URL, Short URL, QR Code, Auto Number, Random, Constant, Terminus ID) are generated by the system from other fields, so a submitter never types their value directly. This part adds three user-input fields. Part 7 adds the computed chain on top of them.

<Note>
  There is no generic "number" or "true/false" field type. A sequential counter is the Auto Number computed type, and a fixed value is the Constant computed type. See the [field reference](/reference/fields) for the full catalog.
</Note>

### Common field settings

A few settings show up on most user-input fields:

* **Required**: a submission with this field blank cannot be approved.
* **Unique**: two approved records in the same taxonomy cannot share the same value.
* **Default**: the starting value when someone opens a new row.
* **Description**: help text shown to the submitter under the field.

### Text transforms

Text fields can reshape whatever the user types before it is stored. Two transforms matter for this tutorial:

* **Case set to lowercase** (`transform_case: lowercase`): "Black Friday 2026" becomes `black friday 2026`.
* **Spaces set to replace with underscore** (`transform_spaces: replace_underscore`): `black friday 2026` becomes `black_friday_2026`.

Applied together, `campaign_name = "Black Friday 2026"` is stored as `black_friday_2026`. That is what makes it safe to drop into a URL in Part 7 without manual cleanup.

## Step by step

<Steps>
  <Step title="Open the new-field form">
    Open the `Marketing` model, go to **Fields**, then create a new field.
  </Step>

  <Step title="Create campaign_name (Text)">
    Name: `campaign_name`. Type: **Text**. Turn on **Required** and **Unique**. Under the text transforms, set **Case** to lowercase and **Spaces** to replace with underscore. Save. The field appears in the model's field list.

    Worked example: a user types `Black Friday 2026`. The transforms fire in order, case first (`black friday 2026`), then spaces (`black_friday_2026`). The stored value is `black_friday_2026`.
  </Step>

  <Step title="Create launch_date (Date)">
    Create another field. Name: `launch_date`. Type: **Date**. Build the display format from the date components in the order year, month, day, with no separator, so a date renders as `20261115`. Set **Default** to `today` and **Earliest allowed date** to `today`. Save. The new field appears below `campaign_name`.
  </Step>

  <Step title="Create destination_url (URL)">
    Create one more field. Name: `destination_url`. Type: **URL**. Under allowed protocols, keep `https` and turn off `http` so only secure addresses are accepted. Leave the default value blank. Save. The third field appears in the list.
  </Step>
</Steps>

<Note>Screenshot coming soon: Part 3 field list.</Note>

## Check your work

* Three fields exist on the `Marketing` model: `campaign_name`, `launch_date`, `destination_url`.
* `campaign_name` is Text with required, unique, lowercase, and replace-spaces-with-underscore set.
* `launch_date` is Date, formatted year-month-day with no separator (so `20261115`), with `today` as both the default and the earliest allowed date.
* `destination_url` is URL and accepts only `https` addresses.
* None of the fields are connected to a taxonomy yet. That happens in Part 8.

## What you just built

The model has its first three fields. None are computed and none are on a form yet. They are sitting in the shared field library, waiting to be referenced.

```mermaid theme={null}
flowchart LR
  subgraph GM[Marketing model]
    direction TB
    CN[campaign_name<br/>Text, required, unique<br/>lowercase, underscore spaces]
    LD[launch_date<br/>Date, YYYYMMDD<br/>default and min: today]
    DU[destination_url<br/>URL, https only]
  end
```

## Gotchas

* **Field type cannot change after the field is created.** If you picked the wrong type, delete the field and create it again. There is no "change type" control.
* **Required with no default means the submission cannot be approved until a value is entered.** `launch_date` sidesteps this because its default is `today`. `campaign_name` has no default, so the submitter must type something.
* **Lowercase applies to the stored value, not just the display.** Once saved, the value really is lowercase, and computed fields downstream see the transformed version, not the original.
* **A URL field that allows only `https` rejects `http://` input** at validation time. This is deliberate: tagged URLs should not fall back to insecure destinations.

## Next up

[Part 4: Static picklists](/tutorial/04-static-picklists). Create the `Goals` picklist and its `goal` dropdown field. For everything a field type can do, see the [field reference](/reference/fields).
