hexojs/hexo · error · Error

Category \`${name}\` has already existed!

Error message

Category \`${name}\` has already existed!

What it means

Thrown by the Category model pre('save') hook (lib/models/category.ts:72). Before a Category document is saved, the hook queries for an existing category with the same name and the same parent (or no parent). If one already exists, the save is aborted to prevent duplicates.

Source

Thrown at lib/models/category.ts:72

  Category.virtual('length').get(function() {
    const ReadOnlyPostCategory = ctx._binaryRelationIndex.post_category;

    return ReadOnlyPostCategory.find({category_id: this._id}).length;
  });

  // Check whether a category exists
  Category.pre('save', (data: CategorySchema) => {
    const { name, parent } = data;
    if (!name) return;

    const Category = ctx.model('Category');
    const cat = Category.findOne({
      name,
      parent: parent || {$exists: false}
    }, {lean: true});

    if (cat) {
      throw new Error(`Category \`${name}\` has already existed!`);
    }
  });

  // Remove PostCategory references
  Category.pre('remove', (data: CategorySchema) => {
    const PostCategory = ctx.model('PostCategory');
    return PostCategory.remove({category_id: data._id});
  });

  return Category;
};

View on GitHub (pinned to 059cb17494)

Solutions

  1. Before insert, query: const exists = ctx.model('Category').findOne({ name, parent: parent || { $exists: false } }, { lean: true }); skip if exists.
  2. Use the post category helper / front-matter flow which dedupes automatically.
  3. Clear the database (hexo clean) before re-importing.
  4. Make category names unique or merge into the existing one instead of creating a new one.

Example fix

// before
ctx.model('Category').insert({ name, parent });

// after
const Category = ctx.model('Category');
const dup = Category.findOne({ name, parent: parent || { $exists: false } }, { lean: true });
if (!dup) Category.insert({ name, parent });
Defensive patterns

Strategy: validation

Validate before calling

const Category = hexo.model('Category');
const dup = Category.findOne({ name, parent: parent || { $exists: false } }, { lean: true });
if (dup) {
  throw new Error(`Category '${name}' already exists (id=${dup._id})`);
}
Category.insert({ name, parent });

Try / catch

try {
  Category.insert({ name, parent });
} catch (e) {
  if (/has already existed/.test(e.message)) {
    return Category.findOne({ name, parent: parent || { $exists: false } }, { lean: true });
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling ctx.model('Category').insert({ name: 'Foo', parent: ... }) when a Category row with name 'Foo' and the same parent already exists; re-importing posts whose front-matter categories collide; a plugin inserting categories without dedup.

Common situations: Running an import twice without clearing the database; two posts defining the same category with a parent that already registered; programmatic category creation in a generator that does not check first; stale db.json from a previous run.

Related errors


AI-assisted analysis of hexojs/hexo@059cb17494 (2026-08-12). Data as JSON: /api/errors/569d6314a8840adb. Report an issue: GitHub.