Skip to main content
A model is a class that represents a table in the database. Each instance of the model corresponds to a row. Sequelize provides two approaches to define models: the Model.init() class-based approach and TypeScript decorators.

Extending the Model class

Every model must extend the base Model class and describe its attributes. Using InferAttributes and InferCreationAttributes gives you automatic TypeScript inference of the instance type and the creation type.
import {
  Model,
  InferAttributes,
  InferCreationAttributes,
  CreationOptional,
  DataTypes,
  Sequelize,
} from '@sequelize/core';

class User extends Model<
  InferAttributes<User>,
  InferCreationAttributes<User>
> {
  // CreationOptional marks fields that can be omitted on create
  declare id: CreationOptional<number>;
  declare username: string;
  declare email: string;
  declare createdAt: CreationOptional<Date>;
  declare updatedAt: CreationOptional<Date>;
}

Approach 1: Model.init()

Model.init() registers a model with its attribute definitions and options. It must be called before any queries are run against the model.
const sequelize = new Sequelize('sqlite::memory:');

User.init(
  {
    id: {
      type: DataTypes.INTEGER,
      primaryKey: true,
      autoIncrement: true,
    },
    username: {
      type: DataTypes.STRING(100),
      allowNull: false,
      unique: true,
    },
    email: {
      type: DataTypes.STRING,
      allowNull: false,
      unique: true,
    },
    role: {
      type: DataTypes.ENUM('admin', 'user', 'guest'),
      defaultValue: 'user',
    },
  },
  {
    sequelize,
    tableName: 'users',
    timestamps: true,
    underscored: true,
  },
);

Approach 2: TypeScript decorators

Decorators let you co-locate attribute metadata with the class property declaration. You must register the model with the Sequelize instance after defining it.
Decorator support requires experimentalDecorators: true in your tsconfig.json.
import {
  Model,
  InferAttributes,
  InferCreationAttributes,
  CreationOptional,
  DataTypes,
  Sequelize,
} from '@sequelize/core';
import {
  Attribute,
  PrimaryKey,
  AutoIncrement,
  NotNull,
  Default,
  Unique,
  Table,
} from '@sequelize/core/decorators-legacy';

@Table({ tableName: 'users', timestamps: true, underscored: true })
class User extends Model<
  InferAttributes<User>,
  InferCreationAttributes<User>
> {
  @Attribute(DataTypes.INTEGER)
  @PrimaryKey
  @AutoIncrement
  declare id: CreationOptional<number>;

  @Attribute(DataTypes.STRING(100))
  @NotNull
  @Unique
  declare username: string;

  @Attribute(DataTypes.STRING)
  @NotNull
  @Unique
  declare email: string;

  @Attribute(DataTypes.ENUM('admin', 'user', 'guest'))
  @Default('user')
  declare role: 'admin' | 'user' | 'guest';

  declare createdAt: CreationOptional<Date>;
  declare updatedAt: CreationOptional<Date>;
}

const sequelize = new Sequelize('sqlite::memory:', {
  models: [User],
});

Model options

These options are passed as the second argument to Model.init(), or as the argument to the @Table decorator.
OptionTypeDefaultDescription
tableNamestringpluralized model nameThe SQL table name.
freezeTableNamebooleanfalseIf true, Sequelize will not pluralize the model name when deriving the table name. Has no effect if tableName is set.
underscoredbooleanfalseIf true, column names are automatically converted to snake_case.
schemastringDatabase schema to place the table in.
OptionTypeDefaultDescription
timestampsbooleantrueAutomatically adds createdAt and updatedAt columns.
paranoidbooleanfalseInstead of deleting rows, sets a deletedAt timestamp. Requires timestamps: true.
createdAtstring | false'createdAt'Override or disable the createdAt column name.
updatedAtstring | false'updatedAt'Override or disable the updatedAt column name.
deletedAtstring | false'deletedAt'Override or disable the deletedAt column name. Requires paranoid: true.
OptionTypeDescription
validateobjectModel-wide validators. Each key is a validator name, and the value is a function with this bound to the model instance.

Attribute options

Each attribute in Model.init() can be either a bare DataType or a full options object.
OptionTypeDefaultDescription
typeDataTyperequiredThe data type for this column.
allowNullbooleantrueIf false, adds a NOT NULL constraint and runs a not-null validation.
defaultValueunknownA literal value, a JS function, or DataTypes.NOW.
uniqueboolean | stringfalseAdds a unique constraint. Pass a string to create a named composite unique index.
primaryKeybooleanfalseMarks this column as the primary key.
autoIncrementbooleanfalseMakes the column auto-increment.
columnNamestringattribute nameThe actual column name in SQL (formerly field).
commentstringSQL comment on the column.
validateColumnValidateOptionsPer-attribute validators.

Shorthand vs full options

// Shorthand: just the data type
{ username: DataTypes.STRING }

// Full options
{
  username: {
    type: DataTypes.STRING(100),
    allowNull: false,
    unique: true,
    defaultValue: 'anonymous',
    columnName: 'user_name',   // maps JS attribute to SQL column
  }
}

Primary keys

Sequelize automatically adds an integer primary key named id if no primary key is defined. To define your own:
User.init({
  id: {
    type: DataTypes.INTEGER,
    primaryKey: true,
    autoIncrement: true,
  },
}, { sequelize });
For UUID primary keys:
User.init({
  id: {
    type: DataTypes.UUID,
    primaryKey: true,
    defaultValue: DataTypes.UUIDV4,
  },
}, { sequelize });

Default values

// Static default
{ status: { type: DataTypes.STRING, defaultValue: 'active' } }

// Current timestamp
{ createdAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW } }

// Function evaluated on each create (JavaScript-side)
{ token: { type: DataTypes.STRING, defaultValue: () => crypto.randomUUID() } }

Column aliasing

Use columnName (or the deprecated field) to map a JavaScript attribute name to a different SQL column name:
{
  firstName: {
    type: DataTypes.STRING,
    columnName: 'first_name',   // stored as first_name in SQL
  }
}
When underscored: true is set on the model, Sequelize handles this mapping automatically for all attributes.

Syncing models to the database

sequelize.sync() creates or updates tables to match your model definitions. This is convenient for development but should not be used in production — use migrations instead.
// Create tables that don't exist yet (leaves existing tables alone)
await sequelize.sync();

// Drop and recreate all tables (destructive — development only)
await sequelize.sync({ force: true });

// ALTER existing tables to match the model (use with care)
await sequelize.sync({ alter: true });
Do not use sync({ force: true }) or sync({ alter: true }) against a production database. Use a migration tool such as Umzug to manage schema changes safely.

Full example

The following shows a complete model definition using both approaches side by side.
import {
  Model,
  InferAttributes,
  InferCreationAttributes,
  CreationOptional,
  DataTypes,
} from '@sequelize/core';

class Post extends Model<
  InferAttributes<Post>,
  InferCreationAttributes<Post>
> {
  declare id: CreationOptional<number>;
  declare title: string;
  declare body: string | null;
  declare published: boolean;
  declare authorId: number;
  declare createdAt: CreationOptional<Date>;
  declare updatedAt: CreationOptional<Date>;
}

Post.init(
  {
    id: {
      type: DataTypes.INTEGER,
      primaryKey: true,
      autoIncrement: true,
    },
    title: {
      type: DataTypes.STRING(255),
      allowNull: false,
    },
    body: {
      type: DataTypes.TEXT,
      allowNull: true,
    },
    published: {
      type: DataTypes.BOOLEAN,
      allowNull: false,
      defaultValue: false,
    },
    authorId: {
      type: DataTypes.INTEGER,
      allowNull: false,
      columnName: 'author_id',
    },
  },
  {
    sequelize,
    tableName: 'posts',
    timestamps: true,
    underscored: true,
  },
);

Build docs developers (and LLMs) love