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

# Models

> Learn how to create and use models in IlanaORM

## What is a Model?

Think of a model as a **blueprint** for your data. If you have a "users" table in your database, you create a "User" model to work with user data in your JavaScript code.

<Info>
  **Real-world analogy:** A model is like a form at the doctor's office. The form has specific fields (name, age, symptoms), and each filled-out form represents one patient record. The model defines what fields exist, and each instance represents one record.
</Info>

## Creating Your First Model

### Using the CLI (Recommended)

```bash theme={null}
# Create a model
npx ilana make:model User

# Create a model with migration
npx ilana make:model User --migration

# Create a model with everything (migration, factory, seeder)
npx ilana make:model User --all
```

### Manual Creation

<CodeGroup>
  ```javascript models/User.js theme={null}
  const Model = require('ilana-orm/orm/Model');

  class User extends Model {
    // Table name (optional - defaults to lowercase plural)
    static table = 'users';
    
    // Enable timestamps (created_at, updated_at)
    static timestamps = true;
    
    // Fields that can be mass-assigned
    fillable = ['name', 'email', 'password'];
    
    // Fields to hide from JSON output
    hidden = ['password'];
  }

  module.exports = User;
  ```

  ```typescript models/User.ts theme={null}
  import Model from 'ilana-orm/orm/Model';

  export default class User extends Model {
    protected static table = 'users';
    protected static timestamps = true;
    
    protected fillable: string[] = ['name', 'email', 'password'];
    protected hidden: string[] = ['password'];
    
    // Type your attributes
    id!: number;
    name!: string;
    email!: string;
    password!: string;
    created_at!: Date;
    updated_at!: Date;
  }
  ```
</CodeGroup>

## Model Configuration

### Table Settings

```javascript theme={null}
class User extends Model {
  // Specify table name (defaults to lowercase plural of class name)
  static table = 'users';
  
  // Primary key column (defaults to 'id')
  static primaryKey = 'id';
  
  // Primary key type ('number' or 'string')
  static keyType = 'number';
  
  // Whether primary key auto-increments (true for numbers, false for UUIDs)
  static incrementing = true;
  
  // Database connection to use (optional)
  static connection = 'mysql';
}
```

### UUID Primary Keys

Set `keyType = 'uuid'` and `incrementing = false` — IlanaORM generates a UUID automatically on create:

```javascript theme={null}
class User extends Model {
  static table = 'users';
  static keyType = 'uuid';
  static incrementing = false;
}

const user = await User.create({ name: 'John Doe', email: 'john@example.com' });
console.log(user.id); // "550e8400-e29b-41d4-a716-446655440000"
```

### ULID Primary Keys

ULIDs are 26-character sortable IDs — they're URL-safe, lexicographically ordered, and include a millisecond timestamp prefix so they sort naturally by creation time:

```javascript theme={null}
class Order extends Model {
  static table = 'orders';
  static keyType = 'ulid';
  static incrementing = false;
}

const order = await Order.create({ total: 49.99 });
console.log(order.id); // "01J3X7KQZB8YTPNMCHW4RSVFGE"
```

Use `char(26)` for the column type in migrations:

```javascript theme={null}
export async function up(schema) {
  await schema.createTable('orders', (table) => {
    table.string('id', 26).primary();
    table.decimal('total', 10, 2);
    table.timestamps();
  });
}
```

### Timestamps

Control automatic timestamp handling:

```javascript theme={null}
class User extends Model {
  // Enable/disable timestamps
  static timestamps = true;
  
  // Customize timestamp column names
  static createdAt = 'created_at';
  static updatedAt = 'updated_at';
}
```

### Soft Deletes

Mark records as deleted without actually removing them:

```javascript theme={null}
class User extends Model {
  static softDeletes = true;
  static deletedAt = 'deleted_at'; // Column name for soft delete timestamp
}

// Usage
await user.delete(); // Sets deleted_at timestamp
const users = await User.all(); // Only returns non-deleted users
const allUsers = await User.withTrashed().all(); // Includes deleted users
const deletedUsers = await User.onlyTrashed().all(); // Only deleted users
```

## Mass Assignment Protection

Control which fields can be set when creating or updating records:

```javascript theme={null}
class User extends Model {
  // Fields that CAN be mass-assigned
  fillable = ['name', 'email', 'password'];
  
  // OR fields that CANNOT be mass-assigned
  guarded = ['id', 'is_admin', 'created_at'];
  
  // OR disable all protection (not recommended)
  // static unguarded = true;
}

// This works
const user = await User.create({
  name: 'John',
  email: 'john@example.com',
  password: 'secret',
});

// This would be ignored (not in fillable)
const user = await User.create({
  name: 'John',
  is_admin: true, // Ignored!
});
```

<Warning>
  **Security Note:** Always use `fillable` or `guarded` to prevent users from setting sensitive fields like `is_admin` or `balance`.
</Warning>

## Attribute Casting

Automatically convert database values to JavaScript types:

```javascript theme={null}
class User extends Model {
  casts = {
    // Convert to Date objects
    email_verified_at: 'date',
    birth_date: 'date',
    
    // Convert to booleans
    is_admin: 'boolean',
    is_active: 'boolean',
    
    // Convert to numbers
    age: 'number',
    salary: 'float',
    
    // Parse JSON
    preferences: 'json',
    metadata: 'object',
    tags: 'array',
  };
}

// Usage
const user = await User.find(1);
console.log(user.is_admin); // true (boolean, not "1" or 1)
console.log(user.preferences); // { theme: 'dark' } (object, not JSON string)
console.log(user.email_verified_at); // Date object
```

### Available Cast Types

| Cast Type   | Description                                                                        | Example                         |
| ----------- | ---------------------------------------------------------------------------------- | ------------------------------- |
| `'boolean'` | Converts to true/false                                                             | `"1"` → `true`                  |
| `'number'`  | Converts to integer                                                                | `"42"` → `42`                   |
| `'float'`   | Converts to decimal                                                                | `"3.14"` → `3.14`               |
| `'string'`  | Converts to string                                                                 | `42` → `"42"`                   |
| `'date'`    | Passes value through as-is (no JS Date conversion — use `DateCast` class for that) | `"2023-12-01"` → `"2023-12-01"` |
| `'json'`    | Parses JSON string                                                                 | `'{"a":1}'` → `{a:1}`           |
| `'object'`  | Same as json                                                                       | `'{"a":1}'` → `{a:1}`           |
| `'array'`   | Parses JSON array                                                                  | `'[1,2,3]'` → `[1,2,3]`         |

### Cast Class Instances

You can also pass cast class instances (objects with `get(value)` / `set(value)` methods). They are called **automatically** the same way string casts are:

```javascript theme={null}
const { MoneyCast, JsonCast, DateCast } = require('ilana-orm/orm/CustomCasts');

class Product extends Model {
  casts = {
    price:        new MoneyCast(),   // stores cents, returns dollars
    metadata:     new JsonCast(),
    released_at:  new DateCast(),    // returns JS Date object
  };
}

// get() is called automatically on read, set() on write
const product = await Product.find(1);
console.log(product.price); // 19.99 (MoneyCast.get() called automatically)
product.price = 29.99;      // MoneyCast.set() called automatically, stored as 2999
```

See the [Custom Casting](/advanced/casting) guide to build your own cast classes.

## Default Values

Set default values for new model instances:

```javascript theme={null}
class User extends Model {
  attributes = {
    is_active: true,
    role: 'user',
    preferences: { theme: 'light' },
  };
}

// Usage
const user = new User();
console.log(user.is_active); // true
console.log(user.role); // 'user'
```

## Hidden Attributes

Hide sensitive fields from JSON output:

```javascript theme={null}
class User extends Model {
  hidden = ['password', 'remember_token', 'api_key'];
}

// Usage
const user = await User.find(1);
console.log(user.toJSON()); // password field won't be included

// Temporarily show hidden fields
console.log(user.makeVisible(['password']).toJSON());
```

## Mutators and Accessors

Transform data when setting or getting attributes:

### Mutators (Setters)

Mutators are called **automatically** whenever an attribute is set — on direct assignment (`user.email = x`), via `fill()`, and via `update()`. They must return the transformed value.

```javascript theme={null}
class User extends Model {
  // Called automatically any time password is assigned
  setPasswordAttribute(value) {
    return value ? bcrypt.hashSync(value, 10) : value;
  }
  
  // Called automatically any time email is assigned
  setEmailAttribute(value) {
    return value ? value.toLowerCase().trim() : value;
  }
  
  setNameAttribute(value) {
    return value ? value.replace(/\w\S*/g, (txt) => 
      txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()
    ) : value;
  }
}

// Mutators fire automatically — no special call needed
const user = await User.create({
  name: 'john doe',               // Stored as "John Doe"
  email: ' JOHN@EXAMPLE.COM ',   // Stored as "john@example.com"
  password: 'secret123',          // Stored hashed
});

user.email = 'NEW@EXAMPLE.COM';   // Mutator fires on assignment too
console.log(user.email);          // "new@example.com"
```

### Accessors (Getters)

Accessors are called **automatically** on direct property access. List the key in `appends` to include it in `toJSON()` output.

```javascript theme={null}
class User extends Model {
  // Keys in appends are accessible as direct properties and included in toJSON()
  appends = ['full_name', 'avatar_url', 'formatted_created_at'];

  getFullNameAttribute() {
    return `${this.first_name} ${this.last_name}`;
  }
  
  getAvatarUrlAttribute() {
    return this.avatar 
      ? `/uploads/avatars/${this.avatar}`
      : '/images/default-avatar.png';
  }
  
  getFormattedCreatedAtAttribute() {
    return this.created_at.toLocaleDateString();
  }
}

const user = await User.find(1);
console.log(user.full_name);             // "John Doe" — accessor called directly
console.log(user.avatar_url);            // "/uploads/avatars/john.jpg"
console.log(user.formatted_created_at);  // "12/1/2023"

// Also included in toJSON() because they're in appends
const userData = user.toJSON();
console.log(userData.full_name);         // "John Doe"

// Dynamically add an accessor key to appends at runtime
user.append('full_name');
```

## Model Events

Hook into the model lifecycle:

```javascript theme={null}
class User extends Model {
  static {
    // Before saving (create or update)
    this.saving(async (user) => {
      user.email = user.email.toLowerCase();
    });
    
    // After saving
    this.saved(async (user) => {
      await clearUserCache(user.id);
    });
    
    // Before creating
    this.creating(async (user) => {
      user.uuid = generateUuid();
    });
    
    // After creating
    this.created(async (user) => {
      await sendWelcomeEmail(user.email);
    });
    
    // Before updating
    this.updating(async (user) => {
      if (user.isDirty('email')) {
        user.email_verified_at = null;
      }
    });
    
    // After updating
    this.updated(async (user) => {
      await syncUserData(user);
    });
    
    // Before deleting
    this.deleting(async (user) => {
      await user.posts().delete();
    });
    
    // After deleting
    this.deleted(async (user) => {
      await cleanupUserFiles(user.id);
    });
  }
}
```

<Info>
  **Available Events:**

  * `saving` / `saved` - Before/after create or update
  * `creating` / `created` - Before/after create
  * `updating` / `updated` - Before/after update
  * `deleting` / `deleted` - Before/after delete
  * `restoring` / `restored` - Before/after soft delete restore
</Info>

### Muting Events

Sometimes you need to save records without triggering events — for example in seeders, migrations, or bulk imports. Use `withoutEvents()`:

```javascript theme={null}
await User.withoutEvents(async () => {
  await User.create({ name: 'Admin', email: 'admin@example.com' });
  await User.create({ name: 'Bot', email: 'bot@example.com' });
  // creating/created/saving/saved events are all suppressed
});

// Events fire normally again after this point
```

## Query Scopes

Create reusable query constraints:

```javascript theme={null}
class Post extends Model {
  // Simple scope
  static scopePublished(query) {
    return query.where('is_published', true);
  }
  
  // Scope with parameters
  static scopeOfType(query, type) {
    return query.where('type', type);
  }
  
  // Complex scope
  static scopePopular(query, threshold = 100) {
    return query
      .where('views', '>', threshold)
      .orderBy('views', 'desc');
  }
  
  // Date-based scope
  static scopeRecent(query, days = 7) {
    const date = new Date();
    date.setDate(date.getDate() - days);
    return query.where('created_at', '>', date);
  }
}

// Usage
const posts = await Post.query()
  .published()           // Uses scopePublished
  .ofType('article')     // Uses scopeOfType
  .popular(500)          // Uses scopePopular with parameter
  .recent(30)            // Uses scopeRecent with parameter
  .get();
```

## Working with Model Instances

### Creating Records

```javascript theme={null}
// Method 1: create() - saves immediately
const user = await User.create({
  name: 'John Doe',
  email: 'john@example.com',
});

// Method 2: new + save()
const user = new User({
  name: 'John Doe',
  email: 'john@example.com',
});
await user.save();

// Method 3: make() - doesn't save
const user = User.make({
  name: 'John Doe',
  email: 'john@example.com',
});
// ... do something with user
await user.save();
```

### Reading Records

```javascript theme={null}
// Find by primary key
const user = await User.find(1);
const user = await User.findOrFail(1); // Throws error if not found

// Find by other attributes
const user = await User.findBy('email', 'john@example.com');

// Get first record
const user = await User.first();
const user = await User.firstOrFail();

// Get all records
const users = await User.all();

// Find or create
const user = await User.firstOrCreate(
  { email: 'john@example.com' },  // Search criteria
  { name: 'John Doe' }            // Additional data if creating
);

// Update or create
const user = await User.updateOrCreate(
  { email: 'john@example.com' },  // Search criteria
  { name: 'John Smith', is_active: true } // Data to update/create
);
```

### Updating Records

```javascript theme={null}
// Update single record
const user = await User.find(1);
user.name = 'John Smith';
await user.save();

// Or use update method
await user.update({ name: 'John Smith' });

// Update multiple records
await User.query()
  .where('is_active', false)
  .update({ is_active: true });

// Increment/decrement — no fetch needed, updates DB and local attribute
await user.increment('login_count');       // login_count + 1
await user.increment('points', 10);        // points + 10
await user.decrement('credits', 5);        // credits - 5

// Bulk increment/decrement via query builder
await User.query().where('role', 'admin').increment('credits', 100);
```

### Deleting Records

```javascript theme={null}
// Delete single record
const user = await User.find(1);
await user.delete();

// Delete multiple records
await User.query()
  .where('is_active', false)
  .delete();

// Soft delete (if enabled)
await user.delete(); // Sets deleted_at
await user.restore(); // Removes deleted_at

// Bulk restore — restore many at once
await User.query().onlyTrashed().where('role', 'admin').restore();

// Force delete (permanent)
await user.forceDelete();
```

### Pruning Models

Pruning lets you automatically delete stale records (e.g. old logs, expired tokens) by defining what "prunable" means on the model:

```javascript theme={null}
class ActivityLog extends Model {
  static table = 'activity_logs';

  // Return a query for records that should be deleted
  static prunable() {
    return this.query().where('created_at', '<', new Date(Date.now() - 90 * 86400_000));
    // deletes logs older than 90 days
  }
}

// Delete all prunable records — processes in chunks of 1000 to avoid memory issues
const deleted = await ActivityLog.prune();
console.log(`Pruned ${deleted} records`);
```

Run `prune()` on a schedule (e.g. a daily cron job) to keep your tables clean.

## Model Utilities

### Reload and Compare Instances

```javascript theme={null}
// Re-fetch a fresh copy from the database
const user = await User.find(1);
const freshUser = await user.fresh(); // new instance with up-to-date attributes

// Check if two variables refer to the same database record
const a = await User.find(1);
const b = await User.find(1);
console.log(a.is(b));    // true  — same class + same primary key
console.log(a.isNot(b)); // false

const c = await User.find(2);
console.log(a.is(c));    // false
console.log(a.isNot(c)); // true
```

### Replicating Models

`replicate()` creates an unsaved copy of a model, excluding the primary key and timestamps so it can be inserted as a new row:

```javascript theme={null}
const original = await Post.find(1);
const copy = original.replicate();

// copy has same body/title/status — but no id or timestamps
copy.title = 'Copy of ' + original.title;
await copy.save(); // inserts as a new record
```

You can exclude additional columns from the copy:

```javascript theme={null}
const copy = original.replicate(['slug', 'published_at']);
```

### Table Helpers

```javascript theme={null}
// Delete every row in the table (useful in tests / seeders)
await User.truncate();

// Create records using the model's registered factory
await User.seed(10); // insert 10 users via factory
```

<Note>
  `seed()` requires a factory to be defined for the model with `defineFactory`. See [Factories](/essentials/factories) for setup details.
</Note>

### Enum Helpers

Define the possible values for an enum column once, and IlanaORM generates `isX()` / `makeX()` helpers automatically on every instance:

```javascript theme={null}
class User extends Model {
  static enums = {
    role: ['user', 'moderator', 'admin'],
    status: ['active', 'suspended', 'banned'],
  };
}

const user = await User.find(1);

// Check current value
user.isAdmin();      // true / false
user.isModerator();  // true / false
user.isActive();     // true / false

// Change value and save
await user.makeAdmin();     // sets role = 'admin' and saves
await user.makeSuspended(); // sets status = 'suspended' and saves
```

### Check Model State

```javascript theme={null}
const user = await User.find(1);

// Check if model exists in database
console.log(user.exists); // true

// Check if model has been modified
user.name = 'New Name';
console.log(user.isDirty()); // true
console.log(user.isDirty('name')); // true
console.log(user.isDirty('email')); // false

// Get changed attributes
console.log(user.getDirty()); // { name: 'New Name' }

// Get original values
console.log(user.getOriginal()); // Original data from database
console.log(user.getOriginal('name')); // Original name value
```

### Convert to Different Formats

```javascript theme={null}
const user = await User.find(1);

// Convert to plain object (includes appended accessors)
const userData = user.toJSON();

// Convert to JSON string
const jsonString = JSON.stringify(user);

// Get only specific attributes
const publicData = user.only(['id', 'name', 'email']);
const privateData = user.except(['password']);

// Temporarily append accessors
const userWithAccessors = user.append(['full_name', 'avatar_url']).toJSON();
```

## Best Practices

### 1. Use Descriptive Names

```javascript theme={null}
// ✅ Good
class BlogPost extends Model {
  static table = 'blog_posts';
}

// ❌ Avoid
class BP extends Model {
  static table = 'bp';
}
```

### 2. Always Use Mass Assignment Protection

```javascript theme={null}
// ✅ Good
class User extends Model {
  fillable = ['name', 'email', 'password'];
}

// ❌ Dangerous
class User extends Model {
  static unguarded = true; // Anyone can set any field!
}
```

### 3. Hide Sensitive Data

```javascript theme={null}
class User extends Model {
  hidden = ['password', 'remember_token', 'api_key'];
}
```

### 4. Use Appropriate Casting

```javascript theme={null}
class User extends Model {
  casts = {
    email_verified_at: 'date',
    is_admin: 'boolean',
    preferences: 'json',
  };
}
```

### 5. Organize Related Logic

```javascript theme={null}
class User extends Model {
  // Configuration at the top
  static table = 'users';
  static timestamps = true;
  
  // Mass assignment
  fillable = ['name', 'email'];
  hidden = ['password'];
  
  // Casting
  casts = {
    email_verified_at: 'date',
    is_admin: 'boolean',
  };
  
  // Relationships
  posts() {
    return this.hasMany('Post');
  }
  
  // Scopes
  static scopeActive(query) {
    return query.where('is_active', true);
  }
  
  // Mutators/Accessors
  setEmailAttribute(value) {
    return value.toLowerCase();
  }
}
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Learn Queries" icon="magnifying-glass" href="/essentials/queries">
    Discover how to find and filter your data
  </Card>

  <Card title="Set Up Relationships" icon="link" href="/essentials/relationships">
    Connect different models together
  </Card>
</CardGroup>
