# Models

URL: https://zerodev.ca/docs/medusa/developer-documentation/models

> Declare relational database tables, data types, and asynchronous CRUD operations.



Models define SQL database tables managed by Sequelize. When extending `Models`, Medusa automatically creates or alters the corresponding table on startup, translates database constraints into HTTP status codes, and provides promise-based CRUD methods.

Models reside in `src/models/` and load from every `*.js` file located directly in that directory.

## Base Class Definition

```js title="modules/example/src/models/Item.js"
import { Models } from "#medusa/modules";

export class ItemModel extends Models {
    constructor(medusa) {
        super(medusa, {
            name: "example_items",
            dynamic: false
        });
    }

    schema() {
        return {
            guildId: { type: "bigint", required: true },
            userId: { type: "bigint", required: true },
            identifier: { type: "string", required: true, unique: true },
            name: { type: "string", required: true },
            description: { type: "text" },
            quantity: { type: "integer", default: 1 },
            price: { type: "float", default: 0.0 },
            metadata: { type: "json" },
            active: { type: "boolean", default: true },
            expiresAt: { type: "date" }
        };
    }
}
```

## Constructor Options

The configuration object passed to `super(medusa, meta)` accepts the following properties:

| Property   | Type      | Default     | Description                                                                                                                        |
| ---------- | --------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `name`     | `string`  | Required    | Name of the database table.                                                                                                        |
| `dynamic`  | `boolean` | `false`     | When `true`, accepts undeclared fields and serializes them into a JSON `options` column.                                           |
| `formerly` | `string`  | `undefined` | Previous table name. When that table exists and the current one does not, it is renamed before models sync, keeping existing rows. |
| `schema`   | `object`  | `{}`        | Schema definition object. Usually defined via the `schema()` method.                                                               |

## Supported Data Types

Medusa maps schema type strings directly to Sequelize `DataTypes`:

| Type String | Underlying SQL Type        | Usage Guidelines                                                                            |
| ----------- | -------------------------- | ------------------------------------------------------------------------------------------- |
| `"string"`  | `VARCHAR(255)`             | Short text strings, identifiers, and slugs.                                                 |
| `"text"`    | `TEXT`                     | Unbounded text, long descriptions, logs, or formatted notes.                                |
| `"integer"` | `INTEGER`                  | 32-bit signed integers (-2,147,483,648 to 2,147,483,647).                                   |
| `"bigint"`  | `BIGINT`                   | 64-bit integers. Always use this for Discord snowflakes (user IDs, guild IDs, channel IDs). |
| `"float"`   | `FLOAT`                    | Floating-point decimal numbers for balances, multipliers, and ratings.                      |
| `"boolean"` | `BOOLEAN`                  | Flags representing true or false states.                                                    |
| `"date"`    | `TIMESTAMP WITH TIME ZONE` | Timestamps, expiration dates, and scheduling points.                                        |
| `"json"`    | `JSON`                     | Arbitrary JSON objects, arrays, and nested structures.                                      |

## Field Definitions

A field in `schema()` can be declared as an object with constraint options or shorthand as a type string:

```js
schema() {
    return {
        shorthandField: "string",
        detailedField: {
            type: "string",
            required: true,
            unique: true,
            default: "initial"
        }
    };
}
```

### Field Properties

| Property   | Type      | Default     | Description                                                                           |
| ---------- | --------- | ----------- | ------------------------------------------------------------------------------------- |
| `type`     | `string`  | Required    | Data type identifier from the supported types table.                                  |
| `required` | `boolean` | `false`     | When `true`, sets `allowNull: false`. Rejects null or undefined values.               |
| `unique`   | `boolean` | `false`     | When `true`, creates a unique constraint on the column.                               |
| `default`  | `any`     | `undefined` | Default column value applied when creating new records without specifying this field. |

## CRUD Methods

Models expose asynchronous methods called directly or from within [handlers](/docs/medusa/developer-documentation/handlers):

### `create(values)`

Creates and persists a single record in the database.

* **Parameters**: `values` (`object`) - Key-value map of attributes matching the schema.
* **Returns**: `Promise<ModelInstance>` - The created Sequelize instance.
* **Errors**: Throws 400 validation error if required fields are missing; throws 409 conflict error if unique constraints are violated.

```js
const record = await model.create({
    guildId: "100000000000000000",
    userId: "200000000000000000",
    identifier: "item-sword-01",
    name: "Iron Sword",
    quantity: 5,
    price: 49.99
});
```

### `get(where)`

Retrieves the first record matching the query criteria.

* **Parameters**: `where` (`object`) - Query filter conditions.
* **Returns**: `Promise<ModelInstance | null>` - The matching record instance or `null` if none found.

```js
const item = await model.get({ identifier: "item-sword-01" });
if (item) {
    console.log(item.name, item.quantity);
}
```

### `list(where)`

Retrieves all records matching query filter conditions.

* **Parameters**: `where` (`object`, optional) - Query filter conditions. Defaults to `{}` (all records).
* **Returns**: `Promise<Array<ModelInstance>>` - Array of matching record instances.

```js
const guildItems = await model.list({ guildId: "100000000000000000", active: true });
```

### `count(where)`

Counts records matching query filter conditions.

* **Parameters**: `where` (`object`, optional) - Query filter conditions. Defaults to `{}` (all records).
* **Returns**: `Promise<number>` - Number of matching records.

```js
const open = await model.count({ status: "open" });
```

### `edit(where, values)`

Updates attributes on records matching the query criteria.

* **Parameters**:
  * `where` (`object`) - Filter conditions identifying records to update.
  * `values` (`object`) - New attribute values to apply.
* **Returns**: `Promise<[number]>` - Array containing the number of affected rows.
* **Errors**: Throws translated errors if constraints are violated.

```js
await model.edit({ identifier: "item-sword-01" }, { quantity: 10, active: false });
```

### `delete(where)`

Deletes records matching query filter conditions.

* **Parameters**: `where` (`object`) - Filter conditions identifying records to remove.
* **Returns**: `Promise<number>` - Number of records deleted.

```js
const deletedCount = await model.delete({ identifier: "item-sword-01" });
```

## Dynamic Fields

When a model sets `dynamic: true` in its constructor, fields passed to `create()` or `edit()` that are not explicitly defined in `schema()` are stored automatically in a JSON `options` column:

```js title="modules/example/src/models/CustomData.js"
export class CustomDataModel extends Models {
    constructor(medusa) {
        super(medusa, { name: "custom_data", dynamic: true });
    }

    schema() {
        return {
            id: { type: "string", required: true, unique: true }
        };
    }
}
```

```js
await model.create({
    id: "entry_1",
    customAttributeA: "value",
    customAttributeB: 42
});
```

The database stores `customAttributeA` and `customAttributeB` inside `options`, eliminating the need to alter table definitions for arbitrary user data.

## Error Translation

Medusa intercepts Sequelize database exceptions and normalizes them into standard errors with an HTTP `status` property:

* **Unique Constraint**: A `SequelizeUniqueConstraintError` becomes an error with `status = 409` and a clear message indicating which attribute conflicted.
* **Validation Failure**: A `SequelizeValidationError` becomes an error with `status = 400` indicating which fields failed requirement checks.

When called from an HTTP route, these errors automatically respond with the corresponding HTTP status code and `{ error: message }` payload.

## Direct Sequelize Model Access

The initialized Sequelize model is accessible through `this.model`. You can execute arbitrary Sequelize queries:

```js
const total = await model.model.count({ where: { active: true } });
const rows = await model.model.findAll({
    where: { guildId },
    order: [["price", "DESC"]],
    limit: 10
});
```
