Skip to the content.

Populate on SQL-based endpoints

See the documentation index for setup and other guides.

SQL-based endpoints (generated with --sql) support a populate query parameter on GET requests. It tells Sequelize to include related records (JOIN) so the response contains the full related row(s) instead of only the foreign key.

The generated template includes one example reference field, toPop, which is a self-reference (e.g. Order → Order). You can add more reference fields and make them work with ?populate=... by defining the column and the Sequelize association, and following the convention below.


How populate is resolved

The controller interprets each value in populate in two ways:

  1. Model name — If the value equals a registered model name (e.g. Orders, Items), it is treated as “include this model”. Use this when you have a relation to another table/model (e.g. Order → User).
  2. Reference field name — If the value is not a model name, it is treated as a column name on the current model (e.g. toPop, userId). The controller then uses a convention: it includes the current model with alias <field>Ref and foreignKey: <field>. So you must define the matching association in your SQL model.

Making a new reference field work with populate

1. Add the column to the schema

In your SQL model file (e.g. src/models/Orders.ts), add a column for the foreign key:

// Example: reference to another Order (self-reference)
parentOrderId: {
  type: DataTypes.INTEGER,
  description: 'Parent order ID',
  mcpDescription: 'Reference to another Order.'
}

// Example: reference to a User model
userId: {
  type: DataTypes.INTEGER,
  description: 'User who created the order',
  mcpDescription: 'Reference to User.'
}

Add the same fields to the TypeScript attributes interface if you have one.

2. Define the Sequelize association

In the same model file, in the associate function, define how the relation is joined:

Self-reference (same table):

// Convention: alias = '<fieldName>Ref', foreignKey = '<fieldName>'
Orders.associate = function (_models: any): void {
  Orders.belongsTo(Orders, { as: 'toPopRef', foreignKey: 'toPop' });
  Orders.belongsTo(Orders, { as: 'parentOrderIdRef', foreignKey: 'parentOrderId' });
};

Reference to another model:

Orders.associate = function (models: any): void {
  Orders.belongsTo(Orders, { as: 'toPopRef', foreignKey: 'toPop' });
  // Use the other model's name (as registered in src/models/index.js)
  Orders.belongsTo(models.Users, { as: 'user', foreignKey: 'userId' });
};

For reference fields (option 2 in “How populate is resolved” above), the controller only knows about the convention: alias = <field>Ref, same model, foreignKey: <field>. So:

So for minimal changes and to “make populate work for other fields” on the same table, stick to self-references and the <field>Ref convention.

3. Use populate in requests


Summary

Goal What to do
Add a self-reference Add column (e.g. parentOrderId), then Model.belongsTo(Model, { as: 'parentOrderIdRef', foreignKey: 'parentOrderId' }). Use ?populate=parentOrderId.
Add a reference to another table Add column (e.g. userId), then Model.belongsTo(models.OtherModel, { as: 'OtherModel', foreignKey: 'userId' }). Use ?populate=OtherModel (model name as registered).
Convention for field-based populate Alias must be <field>Ref, foreignKey must be <field>. The generated controller uses this to build the Sequelize include.

The toPop field in the SQL template is only an example; you can add as many reference fields as you need, following the same pattern and convention.