n8n-io/n8n · error · TypeORMError

Column "${virtualColumn.propertyName}" of Entity "${entityMe

Error message

Column "${virtualColumn.propertyName}" of Entity "${entityMetadata.name}" is defined as VIRTUAL, but Postgres supports only STORED generated columns.

What it means

Thrown by EntityMetadataValidator.validate in the postgres-only branch when any column has asExpression set (i.e. it is a generated column) and generatedType is either unset or 'VIRTUAL'. Postgres supports only STORED generated columns, so a virtual generated column fails validation.

Source

Thrown at packages/@n8n/typeorm/src/metadata-builder/EntityMetadataValidator.ts:132

					throw new DataTypeNotSupportedError(column, normalizedColumn, driver.options.type);
				if (column.length && driver.withLengthColumnTypes.indexOf(normalizedColumn) === -1)
					throw new TypeORMError(
						`Column ${column.propertyName} of Entity ${entityMetadata.name} does not support length property.`,
					);
				if (column.type === 'enum' && !column.enum && !column.enumName)
					throw new TypeORMError(
						`Column "${column.propertyName}" of Entity "${entityMetadata.name}" is defined as enum, but missing "enum" or "enumName" properties.`,
					);
			});

		// Postgres supports only STORED generated columns.
		if (driver.options.type === 'postgres') {
			const virtualColumn = entityMetadata.columns.find(
				(column) =>
					column.asExpression && (!column.generatedType || column.generatedType === 'VIRTUAL'),
			);
			if (virtualColumn)
				throw new TypeORMError(
					`Column "${virtualColumn.propertyName}" of Entity "${entityMetadata.name}" is defined as VIRTUAL, but Postgres supports only STORED generated columns.`,
				);
		}

		// check if relations are all without initialized properties
		const entityInstance = entityMetadata.create(undefined, {
			fromDeserializer: true,
		});
		entityMetadata.relations.forEach((relation) => {
			if (relation.isManyToMany || relation.isOneToMany) {
				// we skip relations for which persistence is disabled since initialization in them cannot harm somehow
				if (relation.persistenceEnabled === false) return;

				// get entity relation value and check if its an array
				const relationInitializedValue = relation.getEntityValue(entityInstance);
				if (Array.isArray(relationInitializedValue)) throw new InitializedRelationError(relation);
			}
		});

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Set generatedType: 'STORED' on the generated column when targeting postgres.
  2. If the column truly must be virtual, choose a different database (postgres will reject it).
  3. Drop the asExpression option if the column is not actually generated.

Example fix

// before
@Column({
  type: 'generated',
  asExpression: 'lower(name)',
  generatedType: 'VIRTUAL',
})
nameLower: string;

// after (postgres)
@Column({
  type: 'generated',
  asExpression: 'lower(name)',
  generatedType: 'STORED',
})
nameLower: string;
Defensive patterns

Strategy: validation

Validate before calling

function assertNoVirtualGeneratedOnPostgres(dataSource: DataSource): string[] {
  if (dataSource.options.type !== 'postgres') return [];
  const problems: string[] = [];
  for (const meta of dataSource.entityMetadatas) {
    for (const col of meta.columns) {
      if (col.asExpression && (!col.generatedType || col.generatedType === 'VIRTUAL')) {
        problems.push(`${meta.targetName}.${col.propertyName} is VIRTUAL generated; postgres needs STORED`);
      }
    }
  }
  return problems;
}

Try / catch

try {
  await dataSource.initialize();
} catch (err) {
  if (err.message.includes('VIRTUAL, but Postgres supports only STORED')) {
    // change generatedType to 'STORED'
  }
  throw err;
}

Prevention

When it happens

Trigger: Declaring a generated column with @Column({ type: 'generated', asExpression: '...', generatedType: 'VIRTUAL' }) (or omitting generatedType) on a postgres DataSource.

Common situations: Sharing entity definitions between MySQL (which supports VIRTUAL) and postgres; defaulting generatedType and assuming it stays STORED; copying a generated-column example written for MySQL.

Related errors


AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12). Data as JSON: /api/errors/65860ab55d855181. Report an issue: GitHub.