can1357/oh-my-pi · error
Parse error: unexpected else
Error message
Parse error: unexpected else
What it means
During parseTemplate(), an `{{else}}` (or `{{else helper}}`) token is only valid while a block (`{{#if}}`, `{{#each}}`, `{{^}}`...) is open. If the block stack is empty when `else` is encountered, there is no block to attach the inverse section to, so the parser throws "Parse error: unexpected else".
Source
Thrown at packages/utils/src/template.ts:253
if (cursor < source.length) target.push({ kind: "text", value: source.slice(cursor) });
break;
}
if (open > cursor) target.push({ kind: "text", value: source.slice(cursor, open) });
if (source.startsWith("{{!--", open)) {
const end = source.indexOf("--}}", open + 6);
if (end < 0) throw new Error("Parse error: unclosed comment");
cursor = end + 4;
continue;
}
const triple = source.startsWith("{{{", open);
const contentStart = open + (triple ? 3 : 2);
const end = findTagEnd(source, contentStart, triple);
const raw = source.slice(contentStart, end).trim();
cursor = end + (triple ? 3 : 2);
if (!raw || raw.startsWith("!")) continue;
if (raw === "else" || raw.startsWith("else ")) {
const current = stack[stack.length - 1];
if (!current) throw new Error("Parse error: unexpected else");
target = current.node.inverse;
if (raw.length > 4) {
const parentTarget = target;
const nested: BlockNode = {
kind: "block",
expression: parseCall(raw.slice(5)),
body: [],
inverse: [],
inverted: false,
};
target.push(nested);
target = nested.body;
stack.push({ node: nested, target: parentTarget, inverted: false });
}
continue;
}
if (raw.startsWith("#") || raw.startsWith("^")) {
const inverted = raw.startsWith("^");View on GitHub (pinned to 9690622007)
Solutions
- Find the `{{else}}` and either delete it or restore the opening block (`{{#if ...}}` / `{{#each ...}}`) it belongs to.
- Check for an extra `{{/if}}` or `{{/each}}` earlier in the template that closes the block before the else.
- Count block opens vs closes (`#`/`^` vs `/`) to confirm the else sits inside exactly one open block.
Example fix
// before
{{#if user}}Hi{{/if}}
{{else}}Anonymous
// after
{{#if user}}Hi{{else}}Anonymous{{/if}} Defensive patterns
Strategy: validation
Validate before calling
function validateElsePlacement(src) {
const tokens = src.match(/\{\{[#/^]?[^}]*\}\}/g) ?? [];
let depth = 0;
for (const t of tokens) {
const raw = t.slice(2, -2).trim();
if (raw === 'else' || raw.startsWith('else ')) {
if (depth === 0) return { ok: false, at: t };
} else if (raw.startsWith('#') || raw.startsWith('^')) depth++;
else if (raw.startsWith('/')) depth--;
}
return { ok: true };
}
const check = validateElsePlacement(src); if (!check.ok) throw new Error(`{{else}} outside block at ${check.at}`); Try / catch
try {
render(src, data);
} catch (err) {
if (err instanceof Error && err.message === 'Parse error: unexpected else') {
throw new Error('Template has {{else}} outside any open block — fix template source');
}
throw err;
} Prevention
- Edit blocks as complete units — when deleting `{{#if}}`, delete its `{{else}}` and `{{/if}}` too.
- Use an editor plugin that highlights Handlebars block pairs.
- Keep templates small; large files make unbalanced blocks harder to spot.
When it happens
Trigger: Parsing a template that contains `{{else}}` outside any open block — e.g. after the enclosing `{{#if ...}}...{{/if}}` was already closed, or a stray `{{else}}` at top level.
Common situations: Manually editing templates and deleting the opening `{{#if}}` while keeping the `{{else}}`; renaming a block and accidentally closing it early; generated templates where conditional logic was removed but the else branch remained.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Parse error: unclosed comment
- Parse error: mismatched ${raw}
- Parse error: unclosed block ${stack[stack.length - 1].node.e
- Missing helper: "${call.name}"
- The partial ${node.expression.name} could not be found
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/89249696b248a2d0.
Report an issue: GitHub.