amark/gun · error
Invalid data: ${check(d)} at ${path}.${path}
Error message
Invalid data: ${check(d)} at ${path}.${path} What it means
Gun's put validation (run in src/put.js) walks each value being written. If a value at some path is neither a valid primitive (valid(d)) nor a Gun graph node (Gun.is(d)) nor a plain object (Object.plain(d)), it is rejected as invalid data and err is raised with the offending value and its graph path. Gun only stores primitives, nodes, and plain objects — class instances, functions, undefined, Dates, etc. are not serializable graph data.
Source
Thrown at src/put.js:32
var s = as.state = as.state || Gun.state();
if('function' == typeof data){ data(function(d){ as.data = d; gun.put(u,u,as) }); return gun }
if(!as.soul){ return get(as), gun }
as.$ = root.$.get(as.soul); // TODO: This may not allow user chaining and similar?
as.todo = [{it: as.data, ref: as.$}];
as.turn = as.turn || turn;
as.ran = as.ran || ran;
//var path = []; as.via.back(at => { at.get && path.push(at.get.slice(0,9)) }); path = path.reverse().join('.');
// TODO: Perf! We only need to stun chains that are being modified, not necessarily written to.
(function walk(){
var to = as.todo, at = to.pop(), d = at.it, cid = at.ref && at.ref._.id, v, k, cat, tmp, g;
stun(as, at.ref);
if(tmp = at.todo){
k = tmp.pop(); d = d[k];
if(tmp.length){ to.push(at) }
}
k && (to.path || (to.path = [])).push(k);
if(!(v = valid(d)) && !(g = Gun.is(d))){
if(!Object.plain(d)){ ran.err(as, "Invalid data: "+ check(d) +" at " + (as.via.back(function(at){at.get && tmp.push(at.get)}, tmp = []) || tmp.join('.'))+'.'+(to.path||[]).join('.')); return }
var seen = as.seen || (as.seen = []), i = seen.length;
while(i--){ if(d === (tmp = seen[i]).it){ v = d = tmp.link; break } }
}
if(k && v){ at.node = state_ify(at.node, k, s, d) } // handle soul later.
else {
if(!as.seen){ ran.err(as, "Data at root of graph must be a node (an object)."); return }
as.seen.push(cat = {it: d, link: {}, todo: g? [] : Object.keys(d).sort().reverse(), path: (to.path||[]).slice(), up: at}); // Any perf reasons to CPU schedule this .keys( ?
at.node = state_ify(at.node, k, s, cat.link);
!g && cat.todo.length && to.push(cat);
// ---------------
var id = as.seen.length;
(as.wait || (as.wait = {}))[id] = '';
tmp = (cat.ref = (g? d : k? at.ref.get(k) : at.ref))._;
(tmp = (d && (d._||'')['#']) || tmp.soul || tmp.link)? resolve({soul: tmp}) : cat.ref.get(resolve, {run: as.run, /*hatch: 0,*/ v2020:1, out:{get:{'.':' '}}}); // TODO: BUG! This should be resolve ONLY soul to prevent full data from being loaded. // Fixed now?
//setTimeout(function(){ if(F){ return } console.log("I HAVE NOT BEEN CALLED!", path, id, cat.ref._.id, k) }, 9000); var F; // MAKE SURE TO ADD F = 1 below!
function resolve(msg, eve){
var end = cat.link['#'];
if(eve){ eve.off(); eve.rid(msg) } // TODO: Too early! Check all peers ack not found.View on GitHub (pinned to 552227599d)
Solutions
- Convert non-plain values to primitives before put: dates → ISO strings or epoch numbers
- Extract plain data from class instances/Maps/Sets (JSON round-trip or explicit toObject)
- Ensure undefined fields are removed or replaced with null/string placeholders
- For large shared objects, store them as their own node (soul) and link by reference instead of nesting
Example fix
// before
user.get('profile').put({ createdAt: new Date(), meta: someClassInstance });
// after
user.get('profile').put({
createdAt: new Date().toISOString(),
meta: JSON.parse(JSON.stringify(someClassInstance))
}); Defensive patterns
Strategy: validation
Validate before calling
function assertPutSafe(value, depth = 0) {
if (value === null || ['string','number','boolean'].includes(typeof value)) return;
if (depth > 5) throw new Error('too deep for gun put');
const proto = Object.getPrototypeOf(value);
if (value instanceof Date || typeof value === 'function' || value instanceof Map || value instanceof Set) {
throw new Error('non-graph value: ' + value);
}
if (proto === Object.prototype || proto === null || Array.isArray(value)) {
Object.values(value).forEach(v => assertPutSafe(v, depth + 1));
return;
}
throw new Error('class instances are not plain objects: ' + value);
} Type guard
const isPutSafeValue = (v) => v === null || ['string','number','boolean'].includes(typeof v) || (typeof v === 'object' && (v.constructor === Object || v instanceof Date === false) && Object.getPrototypeOf(v) === Object.prototype);
Try / catch
gun.get('node').put(data, ack => {
if (ack && ack.err) {
if (String(ack.err).startsWith('Invalid data:')) {
console.error('gun rejected non-plain value at:', ack.err);
}
return;
}
}); Prevention
- Convert Dates to ISO strings/epoch numbers before put
- Serialize class instances, Maps, Sets with JSON round-trip
- Never put functions, undefined, or framework proxies
- Keep node payloads small and plain; link large objects by soul
When it happens
Trigger: gun.put() or gun.get(...).put(...) with a field value that is a Date, function, class instance, undefined, NaN or other non-plain object; deeply nested non-plain values discovered while walking the todo path (path shown as via-path + to-path in the message).
Common situations: Storing Date objects instead of timestamps; storing a Map/Set or class instance returned from an ORM; writing values still wrapped in a Proxy or reactive framework object; accidentally putting a function or undefined from an unset variable; Vue/React state objects passed straight into put.
Related errors
- Data at root of graph must be a node (an object).
- Your user account is not published for dApps to access, plea
- Wrong user or password.
- User cannot be found!
AI-assisted analysis of amark/gun@552227599d (2026-09-02).
Data as JSON: /api/errors/2ae44ab06b0f7ca4.
Report an issue: GitHub.