hexojs/hexo · error · Error

Draft "${slug}" does not exist.

Error message

Draft "${slug}" does not exist.

What it means

Thrown by Post.publish() in lib/hexo/post.ts:505. publish() moves a draft from source/_drafts/ into _posts/: it slugizes data.slug, lists the _drafts directory, and regex-matches a file whose name begins with that slug. If no file matches, it aborts because there is nothing to promote.

Source

Thrown at lib/hexo/post.ts:505

    }

    if (data.layout === 'draft') data.layout = 'post';

    const ctx = this.context;
    const { config } = ctx;
    const draftDir = join(ctx.source_dir, '_drafts');
    const slug = slugize(data.slug.toString(), { transform: config.filename_case });
    data.slug = slug;
    const regex = new RegExp(`^${escapeRegExp(slug)}(?:[^\\/\\\\]+)`);
    let src = '';
    const result: Result = {} as any;

    data.layout = (data.layout || config.default_layout).toLowerCase();

    // Find the draft
    return listDir(draftDir).then(list => {
      const item = list.find(item => regex.test(item));
      if (!item) throw new Error(`Draft "${slug}" does not exist.`);

      // Read the content
      src = join(draftDir, item);
      return readFile(src);
    }).then(content => {
      // Create post
      Object.assign(data, yfmParse(content));
      data.content = data._content;
      data._content = undefined;

      return this.create(data, replace as boolean);
    }).then(post => {
      result.path = post.path;
      result.content = post.content;
      return unlink(src);
    }).then(() => { // Remove the original draft file
      if (!config.post_asset_folder) return;

View on GitHub (pinned to 059cb17494)

Solutions

  1. List source/_drafts/ and confirm a file starting with the slug exists: ls source/_drafts/.
  2. Create the draft first: hexo new draft <name>, then hexo publish <name>.
  3. Verify data.slug is the exact string you expect (print it) and that slugize did not transform it unexpectedly.
  4. Check config.filename_case (0 = no change, 1 = upper, 2 = lower) and align the draft filename to it.
  5. If _drafts is missing, create it or stop calling publish for non-draft content.

Example fix

// before
hexo.post.publish({ slug: userInput }).catch(console.error);

// after
const draftPath = require('path').join(hexo.source_dir, '_drafts', userInput + '.md');
require('hexo-fs').exists(draftPath).then(exists => {
  if (!exists) throw new Error(`No draft at ${draftPath}`);
  return hexo.post.publish({ slug: userInput });
});
Defensive patterns

Strategy: validation

Validate before calling

const path = require('path');
const { exists, listDir } = require('hexo-fs');
const draftDir = path.join(hexo.source_dir, '_drafts');
const draftExists = await exists(path.join(draftDir, slug + '.md'));
if (!draftExists) {
  const list = await listDir(draftDir).catch(() => []);
  throw new Error(`No draft for slug '${slug}'. Drafts: ${list.join(', ')}`);
}
return hexo.post.publish({ slug });

Try / catch

try {
  await hexo.post.publish({ slug });
} catch (e) {
  if (/Draft .* does not exist/.test(e.message)) {
    log.warn(`Skipping publish: ${e.message}`);
  } else throw e;
}

Prevention

When it happens

Trigger: hexo.post.publish({ slug: 'foo' }) when no source/_drafts/foo.md (or foo/something) exists; slug casing differs because config.filename_case transformed it (e.g. 1 uppercases the filename but slugize lowercased the input); slug contains characters that slugize stripped; the draft was already published and removed; _drafts directory itself is missing so listDir returns an empty list.

Common situations: Typo in the slug passed on the CLI or in code; draft created under a different name or in _posts directly; migrating a project where _drafts was not copied; changing filename_case after drafts were created; calling publish programmatically with a slug sourced from front-matter that is empty or whitespace.

Related errors


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