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

# Models

Copper models are plain Go structs. There is no ORM and no base class to extend; a model is simply a struct whose fields are tagged with the columns they map to.

### Defining a Model

Models conventionally live in your package's `models.go`. Columns are mapped to fields using `db:` tags, and you may add `json:` tags alongside them when the same struct shapes your API responses or Inertia props:

```go pkg/rockets/models.go theme={null}
type Rocket struct {
	ID        int64     `db:"id,readonly"        json:"id"`
	CreatedAt time.Time `db:"created_at,readonly" json:"createdAt"`
	Name      string    `db:"name"                json:"name"`
	Fuel      int64     `db:"fuel"                json:"fuel"`
	Note      *string   `db:"note"                json:"note,omitempty"`
}
```

The `db:` tags are used both when scanning rows in [Queries](/database/queries) and when generating SQL fragments with [Query Builder](/database/query-builder).

### Nullable Columns

Pointer fields map to nullable columns. A `NULL` value scans as `nil`, and writing `nil` stores a `NULL`. If you would like to build pointers inline, the `cvars.Ptr` helper saves you a temporary variable:

```go theme={null}
rocket.Note = cvars.Ptr("engine check complete")
```

### The readonly Modifier

The `,readonly` modifier marks columns that are written once and never updated, such as `id` and `created_at`. Readonly columns are included when inserting but excluded when updating, which is enforced by the [Query Builder](/database/query-builder) `SetColumns` and `SetValues` functions.

### Tag Rules

* Fields without a `db:` tag, tagged `db:"-"`, or unexported are skipped.
* Embedded structs are flattened; their tagged fields are included recursively.
* `db:"col,readonly"` includes the column in inserts but excludes it from updates, as described above.

### Custom Column Types

Since scanning is built on `database/sql`, any type that implements `driver.Valuer` and `sql.Scanner` may be used as a column type. This lets a domain type validate and format itself while being stored as a simple column:

```go theme={null}
type LaunchCode string

func (c LaunchCode) Value() (driver.Value, error) {
	return string(c), nil
}

func (c *LaunchCode) Scan(src any) error {
	s, ok := src.(string)
	if !ok {
		return fmt.Errorf("cannot scan %T into LaunchCode", src)
	}

	*c = LaunchCode(s)
	return nil
}
```

### Next

Now that you have a model, you may want to read and write it with [Queries](/database/queries), or generate the SQL fragments from its tags with [Query Builder](/database/query-builder).
