Cascading Delete
Delete a record and all related records across collections.
Lexicon type: procedure
function handle() if not input.uri then return { error = "uri is required" } end
-- Load the primary record local primary = Record.load(input.uri) if not primary then return { error = "not found" } end
-- Find related records that reference this URI local comments = db.query({ collection = "xyz.statusphere.comment", did = caller_did, limit = 100, })
-- Collect records to delete local to_delete = { primary } for _, comment in ipairs(comments.records) do if comment.postUri == input.uri then local r = Record.load(comment.uri) if r then to_delete[#to_delete + 1] = r end end end
-- Delete all matched records for _, r in ipairs(to_delete) do r:delete() end
return { deleted = #to_delete, }endHow it works
Section titled “How it works”- Load the primary record by URI. Return early if it doesn’t exist.
- Query for related records, in this example comments by the same user that reference the primary record’s URI.
- Load each related record with
Record.loadto get a deletableRecordinstance. - Delete everything. Each
r:delete()removes the record from the user’s PDS and the local index.
const response = await fetch("http://127.0.0.1:3000/xrpc/xyz.statusphere.deletePost", { method: "POST", headers: { "X-Client-Key": CLIENT_KEY, Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ uri: "at://did:plc:abc/xyz.statusphere.post/abc123", }),});const data = await response.json();const response = await fetch("http://127.0.0.1:3000/xrpc/xyz.statusphere.deletePost", { method: "POST", headers: { "X-Client-Key": CLIENT_KEY, Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ uri: "at://did:plc:abc/xyz.statusphere.post/abc123", }),});const data = await response.json();let response = client .post("http://127.0.0.1:3000/xrpc/xyz.statusphere.deletePost") .header("X-Client-Key", client_key) .header("Authorization", format!("Bearer {}", token)) .json(&serde_json::json!({ "uri": "at://did:plc:abc/xyz.statusphere.post/abc123" })) .send() .await?;body := `{ "uri": "at://did:plc:abc/xyz.statusphere.post/abc123" }`req, _ := http.NewRequest("POST", "http://127.0.0.1:3000/xrpc/xyz.statusphere.deletePost", bytes.NewBufferString(body))req.Header.Set("X-Client-Key", clientKey)req.Header.Set("Authorization", "Bearer "+token)req.Header.Set("Content-Type", "application/json")resp, err := http.DefaultClient.Do(req)curl -X POST http://127.0.0.1:3000/xrpc/xyz.statusphere.deletePost \ -H "X-Client-Key: $CLIENT_KEY" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "uri": "at://did:plc:abc/xyz.statusphere.post/abc123" }'{ "deleted": 4}Use case
Section titled “Use case”Cascading deletes are useful when your data model has parent-child relationships across collections. For example, deleting a post should also clean up its comments, reactions, or metadata records. This keeps the user’s repo and the local index consistent.
Note that this only deletes records owned by caller_did. atproto records can only be deleted by their owner. If the related records could have more than 100 matches, paginate through all of them before deleting.

