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
- List source/_drafts/ and confirm a file starting with the slug exists: ls source/_drafts/.
- Create the draft first: hexo new draft <name>, then hexo publish <name>.
- Verify data.slug is the exact string you expect (print it) and that slugize did not transform it unexpectedly.
- Check config.filename_case (0 = no change, 1 = upper, 2 = lower) and align the draft filename to it.
- 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
- Always create drafts with 'hexo new draft <name>' before publishing.
- Keep config.filename_case stable to avoid slug/filename case mismatches.
- Print the slug before publish to confirm it matches the draft filename.
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.