Sửa lỗi MongoDB: 'The path must be an array' khi dùng $push hoặc $addToSet

intermediate🍃 MongoDB2026-06-03| MongoDB 4.x / 5.x / 6.x / 7.x, mọi hệ điều hành (Linux, macOS, Windows), với Mongoose, PyMongo hoặc driver gốc

Error Message

The path 'tags' must be an array in the document, but is of type string
#mongodb#push#addToSet#mảng#type-mismatch#schema-validation#mongoose#aggregation

Chuyện gì vừa xảy ra

Bạn chạy lệnh update với $push hoặc $addToSet, muốn thêm một giá trị vào mảng — nhưng MongoDB chặn lại ngay:

MongoServerError: The path 'tags' must be an array in the document, but is of type string

Field đó tồn tại trong document, nhưng nó đang chứa một chuỗi, không phải mảng. $push$addToSet chỉ hoạt động trên mảng thực sự. Thay vì âm thầm làm hỏng dữ liệu, MongoDB từ chối toàn bộ thao tác.

Tái hiện lỗi trong 30 giây

Mở mongosh và chạy đoạn lệnh sau:

// Chèn một document với 'tags' là chuỗi đơn thuần
db.articles.insertOne({ _id: 1, title: "Hello", tags: "mongodb" })

// Thử push thêm một tag khác
db.articles.updateOne({ _id: 1 }, { $push: { tags: "nodejs" } })
// MongoServerError: The path 'tags' must be an array...

Field đang là chuỗi, không phải ["mongodb"]. Đó chính là toàn bộ vấn đề.

Bước 1: Xác nhận kiểu dữ liệu của field trong document bị lỗi

Trước khi động vào bất cứ thứ gì, hãy xem dữ liệu thực tế đang lưu là gì:

db.articles.findOne({ _id: 1 }, { tags: 1 })
// { _id: 1, tags: "mongodb" }  ← chuỗi, không phải mảng

Không chắc có bao nhiêu document bị ảnh hưởng? Tìm tất cả những document có tags không phải mảng:

db.articles.find({
  tags: { $exists: true },
  $expr: { $ne: [{ $type: "$tags" }, "array"] }
})

Chạy lệnh này trước khi thay đổi bất cứ điều gì. Bạn cần nắm toàn bộ bức tranh trước đã.

Bước 2: Sửa các document đang có kiểu dữ liệu sai

Trường hợp A — Field đang chứa một chuỗi đơn

Bọc chuỗi hiện tại vào trong mảng, rồi thêm item mới vào:

// Với một document cụ thể đã biết
const doc = db.articles.findOne({ _id: 1 })
const existingTag = doc.tags  // "mongodb"

db.articles.updateOne(
  { _id: 1 },
  { $set: { tags: [existingTag, "nodejs"] } }
)

// Kiểm tra lại
db.articles.findOne({ _id: 1 }, { tags: 1 })
// { _id: 1, tags: [ "mongodb", "nodejs" ] }

Trường hợp B — Sửa hàng loạt tất cả document bị lỗi

Có hàng trăm document sai? Dùng aggregation pipeline update để bọc tất cả giá trị vô hướng thành mảng — một thao tác duy nhất, không cần vòng lặp ở phía ứng dụng:

db.articles.updateMany(
  {
    tags: { $exists: true },
    $expr: { $ne: [{ $type: "$tags" }, "array"] }
  },
  [
    { $set: { tags: ["$tags"] } }  // pipeline update — bọc giá trị vào []
  ]
)

Cặp dấu ngoặc vuông bao quanh { $set: ... } không phải lỗi đánh máy. Đó là cú pháp aggregation pipeline update (MongoDB 4.2+). Nó cho phép bạn tham chiếu đến chính giá trị field trong document ("$tags") ở vế phải của $set — điều mà biểu thức update thông thường không làm được.

Trường hợp C — Field là null, không tồn tại, hoặc kiểu dữ liệu khác không phải chuỗi

Đôi khi field là null hoặc đơn giản là không tồn tại. Hãy reset nó về mảng rỗng trước, rồi mới push:

// Đặt về [] cho mọi document mà tags chưa phải mảng
db.articles.updateMany(
  { $expr: { $ne: [{ $type: "$tags" }, "array"] } },
  { $set: { tags: [] } }
)

// Bây giờ $push hoạt động bình thường
db.articles.updateMany({}, { $push: { tags: "mongodb" } })

Bước 3: Ngăn lỗi tái diễn

Tùy chọn 1 — JSON Schema validation (khuyến nghị)

Thêm quy tắc schema để MongoDB từ chối mọi thao tác ghi lưu giá trị không phải mảng vào tags:

db.runCommand({
  collMod: "articles",
  validator: {
    $jsonSchema: {
      bsonType: "object",
      properties: {
        tags: {
          bsonType: "array",
          items: { bsonType: "string" },
          description: "tags must be an array of strings"
        }
      }
    }
  },
  validationLevel: "moderate"   // "strict" cũng từ chối cả các document cũ đang sai
})

Từ thời điểm đó, bất kỳ lệnh insert nào gửi tags: "mongodb" thay vì tags: ["mongodb"] sẽ thất bại ngay lập tức với lỗi validation rõ ràng. Bắt lỗi ngay lúc ghi thay vì hàng giờ sau khi $push bắt đầu bùng phát trên production.

Tùy chọn 2 — Ép kiểu bằng Mongoose schema

Nếu bạn dùng Mongoose, khai báo field là mảng một cách tường minh trong schema:

const articleSchema = new mongoose.Schema({
  title: String,
  tags: { type: [String], default: [] }  // luôn là mảng
})

const Article = mongoose.model("Article", articleSchema)

Mongoose tự ép kiểu khi save. Bạn cũng dùng được các method của Mongoose document như doc.tags.push("nodejs") hoạt động chính xác ngay từ đầu — không cần gọi trực tiếp MongoDB driver.

Tùy chọn 3 — Dùng $addToSet kết hợp $each

Khi dữ liệu đã sạch, ưu tiên dùng $addToSet thay vì $push cho các field tag. Nó tự động bỏ qua các giá trị đã có trong mảng:

db.articles.updateOne(
  { _id: 1 },
  { $addToSet: { tags: { $each: ["nodejs", "mongodb"] } } }
)

Kiểm tra xem fix đã hoạt động chưa

// 1. Kiểm tra document cụ thể
db.articles.findOne({ _id: 1 }, { tags: 1 })
// Kỳ vọng: { _id: 1, tags: [ "mongodb", "nodejs" ] }

// 2. Xác nhận không còn field tags nào không phải mảng
db.articles.countDocuments({
  tags: { $exists: true },
  $expr: { $ne: [{ $type: "$tags" }, "array"] }
})
// Kỳ vọng: 0

// 3. Chạy lại $push ban đầu — lần này phải hoạt động được
db.articles.updateOne({ _id: 1 }, { $push: { tags: "express" } })
// Kỳ vọng: { acknowledged: true, matchedCount: 1, modifiedCount: 1 }

Tại sao lỗi cứ tái diễn (và cách ngăn chặn)

  • Nguyên nhân gốc rễ là sai kiểu dữ liệu. $push$addToSet yêu cầu mảng thực sự. Một chuỗi đơn như "mongodb" không đủ điều kiện — thao tác thất bại hoàn toàn thay vì âm thầm tạo ra dữ liệu hỏng.
  • Migration thường là thủ phạm. Phiên bản cũ của ứng dụng lưu một tag dưới dạng chuỗi đơn. Một thay đổi schema sau đó đã bổ sung hỗ trợ nhiều tag bằng mảng, nhưng các document cũ chưa bao giờ được backfill. Hàng nghìn bản ghi bị sai kiểu, và không ai phát hiện ra cho đến khi $push đụng vào một trong số chúng.
  • Aggregation pipeline update là cách fix hàng loạt gọn nhất. Pattern [{ $set: { field: ["$field"] } }] bọc bất kỳ giá trị vô hướng nào thành mảng trong một lần ghi — không cần kéo document về ứng dụng trước.
  • Schema validation đáng để đầu tư từ sớm. MongoDB mặc định lưu bất cứ thứ gì bạn đưa vào. Một quy tắc validator collMod thay đổi điều đó: lỗi kiểu dữ liệu bị bắt ngay lúc ghi thay vì nổi lên thành lỗi runtime khó hiểu nhiều tuần sau.

Related Error Notes