Use when updating existing records in a Bknd entity via the SDK or REST API. Covers updateOne, updateMany, updating relations ($set, $add, $remove, $unset), partial updates, conditional updates, response handling, and common patterns.
npx claudepluginhub cameronapak/bknd-expert --plugin bknd-research-skillsThis skill uses the workspace's default tool permissions.
Update existing records in your Bknd database using the SDK or REST API.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Checks Next.js compilation errors using a running Turbopack dev server after code edits. Fixes actionable issues before reporting complete. Replaces `next build`.
Update existing records in your Bknd database using the SDK or REST API.
UI steps: Admin Panel > Data > Select Entity > Click record > Edit fields > Save
import { Api } from "bknd";
const api = new Api({
host: "http://localhost:7654",
});
// If auth required:
api.updateToken("your-jwt-token");
Use updateOne(entity, id, data):
const { ok, data, error } = await api.data.updateOne("posts", 1, {
title: "Updated Title",
status: "published",
});
if (ok) {
console.log("Updated post:", data.id);
} else {
console.error("Failed:", error.message);
}
The response object:
type UpdateResponse = {
ok: boolean; // Success/failure
data?: { // Updated record (if ok)
id: number;
// ...all fields with new values
};
error?: { // Error info (if !ok)
message: string;
code: string;
};
};
Only changed fields are required - other fields remain unchanged:
// Only update title, keep everything else
await api.data.updateOne("posts", 1, {
title: "New Title Only",
});
// Update multiple fields
await api.data.updateOne("users", 5, {
name: "New Name",
bio: "Updated bio",
updated_at: new Date().toISOString(),
});
// Change post author to user ID 2
await api.data.updateOne("posts", 1, {
author: { $set: 2 },
});
// Remove author link
await api.data.updateOne("posts", 1, {
author: { $unset: true },
});
// Add tags 4 and 5 to existing tags
await api.data.updateOne("posts", 1, {
tags: { $add: [4, 5] },
});
// Remove tag 2 from post
await api.data.updateOne("posts", 1, {
tags: { $remove: [2] },
});
// Replace all tags with new set
await api.data.updateOne("posts", 1, {
tags: { $set: [1, 3, 5] },
});
await api.data.updateOne("posts", 1, {
title: "Updated Post",
status: "published",
author: { $set: newAuthorId },
tags: { $add: [newTagId] },
});
Use updateMany(entity, where, data):
// Archive all draft posts
const { ok, data } = await api.data.updateMany(
"posts",
{ status: { $eq: "draft" } }, // where clause (required)
{ status: "archived" }, // update values
);
// data contains affected records
console.log("Archived", data.length, "posts");
Important: where clause is required to prevent accidental update-all.
// Update posts by author
await api.data.updateMany(
"posts",
{ author_id: { $eq: userId } },
{ author_id: newUserId },
);
// Update old records
await api.data.updateMany(
"sessions",
{ last_active: { $lt: "2024-01-01" } },
{ expired: true },
);
curl -X PATCH http://localhost:7654/api/data/posts/1 \
-H "Content-Type: application/json" \
-d '{"title": "Updated Title", "status": "published"}'
curl -X PATCH http://localhost:7654/api/data/posts/1 \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d '{"title": "Protected Update"}'
# Change author
curl -X PATCH http://localhost:7654/api/data/posts/1 \
-H "Content-Type: application/json" \
-d '{"author": {"$set": 2}}'
# Add tags
curl -X PATCH http://localhost:7654/api/data/posts/1 \
-H "Content-Type: application/json" \
-d '{"tags": {"$add": [4, 5]}}'
curl -X PATCH "http://localhost:7654/api/data/posts?where=%7B%22status%22%3A%22draft%22%7D" \
-H "Content-Type: application/json" \
-d '{"status": "archived"}'
import { useApp } from "bknd/react";
import { useState, useEffect } from "react";
function EditPostForm({ postId }: { postId: number }) {
const { api } = useApp();
const [title, setTitle] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// Load existing data
useEffect(() => {
api.data.readOne("posts", postId).then(({ data }) => {
if (data) setTitle(data.title);
});
}, [postId]);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setLoading(true);
setError(null);
const { ok, error: apiError } = await api.data.updateOne("posts", postId, {
title,
});
setLoading(false);
if (!ok) {
setError(apiError.message);
}
}
return (
<form onSubmit={handleSubmit}>
<input
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Post title"
/>
<button type="submit" disabled={loading}>
{loading ? "Saving..." : "Save"}
</button>
{error && <p className="error">{error}</p>}
</form>
);
}
import useSWR, { mutate } from "swr";
function useUpdatePost() {
const { api } = useApp();
async function updatePost(id: number, updates: object) {
const { ok, data, error } = await api.data.updateOne("posts", id, updates);
if (ok) {
// Revalidate the post and list
mutate(`posts/${id}`);
mutate("posts");
}
return { ok, data, error };
}
return { updatePost };
}
function useOptimisticUpdate() {
const { api } = useApp();
const [posts, setPosts] = useState<Post[]>([]);
async function updatePost(id: number, updates: Partial<Post>) {
// Optimistic: update immediately
const originalPosts = [...posts];
setPosts((prev) =>
prev.map((p) => (p.id === id ? { ...p, ...updates } : p))
);
// Actual update
const { ok } = await api.data.updateOne("posts", id, updates);
if (!ok) {
// Rollback on failure
setPosts(originalPosts);
}
return { ok };
}
return { posts, updatePost };
}
import { Api } from "bknd";
const api = new Api({ host: "http://localhost:7654" });
// Authenticate
await api.auth.login({ email: "user@example.com", password: "password" });
// Simple field update
const { ok, data } = await api.data.updateOne("posts", 1, {
title: "Updated Title",
updated_at: new Date().toISOString(),
});
// Update with relation changes
await api.data.updateOne("posts", 1, {
status: "published",
published_at: new Date().toISOString(),
category: { $set: 3 }, // Change category
tags: { $add: [7, 8] }, // Add new tags
});
// Bulk update: mark old drafts as archived
await api.data.updateMany(
"posts",
{
status: { $eq: "draft" },
created_at: { $lt: "2024-01-01" },
},
{ status: "archived" }
);
// Toggle boolean field
const post = await api.data.readOne("posts", 1);
if (post.ok) {
await api.data.updateOne("posts", 1, {
featured: !post.data.featured,
});
}
async function upsert(
api: Api,
entity: string,
where: object,
data: object
) {
const { data: existing } = await api.data.readOneBy(entity, { where });
if (existing) {
return api.data.updateOne(entity, existing.id, data);
}
return api.data.createOne(entity, data);
}
// Usage
await upsert(
api,
"settings",
{ key: { $eq: "theme" } },
{ key: "theme", value: "dark" }
);
async function updateIf(
api: Api,
entity: string,
id: number,
condition: (record: any) => boolean,
updates: object
) {
const { data: current } = await api.data.readOne(entity, id);
if (!current || !condition(current)) {
return { ok: false, error: { message: "Condition not met" } };
}
return api.data.updateOne(entity, id, updates);
}
// Only update if not published
await updateIf(
api,
"posts",
1,
(post) => post.status !== "published",
{ title: "New Title" }
);
async function increment(
api: Api,
entity: string,
id: number,
field: string,
amount: number = 1
) {
const { data: current } = await api.data.readOne(entity, id);
if (!current) return { ok: false };
return api.data.updateOne(entity, id, {
[field]: current[field] + amount,
});
}
// Increment view count
await increment(api, "posts", 1, "view_count");
// Decrement stock
await increment(api, "products", 5, "stock", -1);
async function softDelete(api: Api, entity: string, id: number) {
return api.data.updateOne(entity, id, {
deleted_at: new Date().toISOString(),
});
}
async function restore(api: Api, entity: string, id: number) {
return api.data.updateOne(entity, id, {
deleted_at: null,
});
}
async function batchUpdate(
api: Api,
entity: string,
updates: Array<{ id: number; data: object }>,
onProgress?: (done: number, total: number) => void
) {
const results = [];
for (let i = 0; i < updates.length; i++) {
const { id, data } = updates[i];
const result = await api.data.updateOne(entity, id, data);
results.push(result);
onProgress?.(i + 1, updates.length);
}
return results;
}
// Usage
await batchUpdate(
api,
"products",
[
{ id: 1, data: { price: 19.99 } },
{ id: 2, data: { price: 29.99 } },
{ id: 3, data: { price: 39.99 } },
],
(done, total) => console.log(`${done}/${total}`)
);
Problem: Update returns no data or error.
Fix: Verify record exists first:
const { data: existing } = await api.data.readOne("posts", id);
if (!existing) {
throw new Error("Post not found");
}
await api.data.updateOne("posts", id, updates);
Problem: FOREIGN KEY constraint failed
Fix: Verify related record exists:
// Wrong - author ID doesn't exist
await api.data.updateOne("posts", 1, { author: { $set: 999 } });
// Correct - verify first
const { data: author } = await api.data.readOne("users", newAuthorId);
if (author) {
await api.data.updateOne("posts", 1, { author: { $set: newAuthorId } });
}
Problem: UNIQUE constraint failed when updating to existing value.
Fix: Check uniqueness before update:
// Check if email already taken by another user
const { data: existing } = await api.data.readOneBy("users", {
where: {
email: { $eq: newEmail },
id: { $ne: currentUserId }, // Exclude current user
},
});
if (existing) {
throw new Error("Email already in use");
}
await api.data.updateOne("users", currentUserId, { email: newEmail });
Problem: Assuming success without verification.
Fix: Always check ok:
// Wrong
const { data } = await api.data.updateOne("posts", 1, updates);
console.log(data.title); // data might be undefined!
// Correct
const { ok, data, error } = await api.data.updateOne("posts", 1, updates);
if (!ok) {
throw new Error(error.message);
}
console.log(data.title);
Problem: Unauthorized error.
Fix: Authenticate first:
await api.auth.login({ email, password });
// or
api.updateToken(savedToken);
await api.data.updateOne("posts", 1, updates);
Problem: Replacing when intending to add.
Fix: Use correct operator:
// $set replaces ALL relations
await api.data.updateOne("posts", 1, { tags: { $set: [5] } });
// Post now has ONLY tag 5
// $add keeps existing and adds new
await api.data.updateOne("posts", 1, { tags: { $add: [5] } });
// Post keeps existing tags AND adds tag 5
Problem: Trying to update all without where.
Fix: Always provide where clause:
// updateMany requires where clause
await api.data.updateMany(
"posts",
{ status: { $eq: "draft" } }, // Required
{ status: "archived" }
);
// To update ALL records, use explicit condition
await api.data.updateMany(
"posts",
{ id: { $gt: 0 } }, // Match all
{ reviewed: true }
);
After updating, verify changes:
const { ok } = await api.data.updateOne("posts", 1, { title: "New Title" });
if (ok) {
const { data } = await api.data.readOne("posts", 1);
console.log("Updated title:", data.title);
}
Or via admin panel: Admin Panel > Data > Select Entity > Find record > Verify fields.
DO:
ok before using response data$add/$remove for incremental relation changesDON'T:
$set for relations when meaning $add