Renaming a schema field does not migrate documents
What you'll see
A developer renames a field in the schema designer - say Customers to Paths - and expects the stored documents to follow. The form shows the new label, new saves carry the new key, and the change looks complete.
Then code that reads the document by key comes back empty for every record saved before the rename:
// #/Users/someone.docly - last saved before the rename
{ "Document": { "Role": "editor", "Customers": "[\"DOPS\"]" } }let user = docly.getFile("#/Users/" + email, false);
user.Paths; // undefined - the record still carries Customers
user.Customers; // "[\"DOPS\"]"Nothing throws. The field simply reads as unset, and whatever it drives - access grants, filtering, display - quietly behaves as though it were never filled in.
What's actually happening
A schema defines the editor: which fields exist, how they render, and what the generated schema.html posts. Documents are stored separately as JSON, and their Document object holds exactly the keys that were present the last time each record was saved.
Renaming a field rewrites the schema. It does not rewrite the stored documents, and there is no back-fill pass over the folder. docly.getFile() returns the raw stored keys, so server-side code keeps seeing the old name until each record is re-saved or rewritten.
The result is a data set split in two: records saved after the rename carry the new key, records saved before carry the old one, and both are valid documents under the same schema. Because a missing key reads as undefined rather than raising, the split surfaces as behaviour that is subtly wrong rather than as an error.
Two details of the field definition make this easier to get wrong:
1. Name is the data key, Field is the label - not the other way round. The key resolves as Name || Field, and the label always comes from Field. A designer-built schema normally leaves Name empty, so the key comes from Field and the two agree. Filling in only one of them during a rename produces a key and a label that disagree, with no warning:
"Name": "Paths", // stored under Paths
"Field": "Folders", // but displayed as FoldersThe generated schema.html is the reliable check - the name= attribute on the input is the key that documents will use. Normalising to Name: "" plus Field: "<key>", matching the other fields, avoids the ambiguity entirely.
2. schema.html is generated, the field definitions are not. Editing the field definitions in the schema .docly by hand is fine. Editing schema.html is not - the server regenerates it from the field definitions, so hand edits there are overwritten.
What to do
Treat a field rename as a migration, not an edit. Each step is safe to leave in place for as long as you need.
1. Read both names first. Deploy this before the schema changes - it is a no-op until then, so it can sit in production while you plan the rest.
// every reader, during the rename
paths: parsePaths(user.Paths || user.Customers)2. Rename the field in the schema. Set Field to the new key and leave Name empty. Verify by checking the name= attribute in the generated form.
3. Switch writes to the new name. Keep accepting the old parameter name from callers you do not control - a browser holding a cached script still posts the old one.
Paths: Paths || Customers || "[]"4. Migrate every record. Swap the key in Document; the value is unchanged. Cover every folder that stores the schema, and remember that templates are shared - one schema can back several instances.
"Customers": "[\"DOPS\"]" -> "Paths": "[\"DOPS\"]"5. Drop the fallbacks once no document carries the old key. Then search code and data for the old name; nothing should match except deliberate history notes.
Check which way the field fails before you start. A field that grants something - access, visibility, membership - reads as empty on an un-migrated record, so the holder loses the grant rather than gaining one. That is the safe direction: a half-finished rename produces support tickets, not exposure. A field that means restrict to X rather than allow X inverts this, and an un-migrated record then widens access instead. Know which one you have.
Do not rename the schema and the code in a single pass with no fallback:
// every record saved before the rename now reads as unset
paths: parsePaths(user.Paths)