Unresolved names in hash expressions render empty

Pitfall By design
A #name# expression in a hash file that resolves to nothing renders as an empty string. Nothing is thrown and nothing is logged — unlike JavaScript, where reading an undeclared identifier raises a ReferenceError. A variable that has fallen out of scope, a misspelled field name and a genuinely empty value all produce identical output, so the page keeps rendering and the only symptom is one blank spot.
Applies to: Hash templatesSchemas

What you'll see

A value renders correctly in one part of a page and comes out blank in another, with no error anywhere. The markup around the blank spot is intact, the console is clean, and the server returns HTTP 200.

Typical shapes:

  • A computed value is shown in the body of the page, but the same #name# inside a <script> block further down produces nothing.
  • A tab- or comma-separated string built for the clipboard or a CSV export loses exactly one field. The separators are still there, so the columns do not shift and the loss is easy to miss.
  • A field renders empty across every document after a schema field was renamed.

What's actually happening

A #name# expression is resolved against the bound document's fields and the variables currently in scope. When the name matches neither, the expression evaluates to an empty string and rendering continues. There is no exception, no partial page, and no entry in the log.

This is the opposite of the JavaScript behaviour developers carry over. In JavaScript, alder where no such binding exists throws a ReferenceError and execution stops — the mistake announces itself. In a hash expression the same mistake is indistinguishable in the output from a field that is legitimately blank.

Three different causes converge on that one symptom:

  • Out of scope. A let declared inside a #if(...) {##}# block is scoped to that block. The directive compiles to a real JavaScript block, so this is ordinary block scoping — but referencing the variable later in the file yields an empty string instead of the error you would get in plain JavaScript.
  • Misspelled or renamed field. Field names are case-sensitive. See Renaming a schema field does not migrate documents — after a rename, stored documents still carry the old key and every reference to the new one renders empty.
  • The value really is empty. Indistinguishable from the two above.

The blank is most damaging where it is least visible: inside a generated string. A clipboard or export payload built as "#A#\t#B#\t#C#" keeps all its separators when #B# resolves to nothing, so the result looks structurally correct and the missing value only surfaces once someone reads the pasted data.

What to do

Declare anything used in more than one place at the top level of the template, not inside the block that happens to display it. Give it a defined fallback so the two cases stay distinguishable:

#{
    let alder = "";
    if (Fodt) {
        alder = floor(div(sub(cint(getdate()."yyyyMMdd"), cint(cdate(Fodt)."yyyyMMdd")), 10000));
    }
}#

A variable declared at top level is visible for the rest of the file, including inside <script> blocks.

When a value renders in one place but not another, suspect name resolution before you suspect the computation. The computation is demonstrably working — you can see its output further up the page. What differs between the two sites is scope.

Prove it rather than reason about it. A throwaway page settles the question in one request:

#{ docly.setMime("text/plain"); }#
#{ let toplevel = "OK"; }#
toplevel later in file:  [#toplevel#]
#if(1==1) {#
#{ let inner = "OK"; }#
inner, inside the if:    [#inner#]
#}#
inner, outside the if:   [#inner#]
never declared:          [#neverDeclared#]

The last two lines both print []. See Use a scratch hash file to test Docly functions, and delete the file when you are done.

Reviewing a template you did not write: check every #name# that is not a document field against where its declaration actually sits. An empty render is not evidence that the name is valid.