Iām trying to convert articles from another (not DatoCMS) cms to DatoCMS. Converting the articles is ok, but I also want to put the into a structure like this:
My problem is that Iām not able to figure out how to update the structure. In my first try I just try to insert the single āMin kontoā in the example.
But the first level (āKundeserviceā) already exists, so Iām trying to create the second level (āMin kontoā). I will then manually place the second and next levels under āKundeserviceā in DatoCMS.
(Recommended) Whatās the URL of the record or model in question?
Being able to see the issue in your real project is you for very helpful for troubleshooting. If you donāt provide the URL upfront, weāll often have to ask you for it anyway, which just slows down our response time for you.
If youāre concerned about privacy, you can email us at support@datocms.com instead of posting here.
(Optional) Do you have any sample code you can provide?
A minimal reproducible example would be best, but anything you can provide (a code snippet, public repo, etc.) would also be helpful.
Could you please share with me the URL for this model so I can make a proper example for you? Without knowing its exact schema (what its fields are), itās hard to know what exactly itās expecting. Itās probably something to do with some combination of tree-like models, modular content fields, and links to other records, but I canāt say for sure without seeing the actual project & model in question.
Just to get you started, though, it looks like you would need some combination of these examples:
If thatās not enough, could you please send me the URL and Iād be happy to take a look and give you an actual, working example? (With your permission, Iāll make a test record there and show you the script?)
You can either DM here on the forum (or just post the URL since itās password-protected anyway), or email us at support@datocms.com and reference this thread.
Thank you! Happy to look into it more once I get the URL
Thanks for the details! Could you please try this instead:
import { buildClient } from "@datocms/cma-client-node";
const token = process.env.DATOCMS_API_TOKEN; // In env var
const environment = 'datocms-support-kundeservice-copy'; // Forked sandbox for testing
export const itemTypePageStructure = '889821';
async function run() {
const client = buildClient({ apiToken: token, environment });
const parent = await client.items.create({
item_type: { type: "item_type", id: itemTypePageStructure },
// To place it in the tree, specify the parent ID
parent_id: 'YIeHxlFNTLqbYHQ7fDCpnQ', // ID of the "Kundeservice" record in this model
// Remove the invalid field. You don't have a "name" field in this model.
// name: 'Parent',
// Instead, since your schema specifies a single-link field, you provide the linked record's ID instead
// That gets used as the title field (per your schema config)
site_structure_content: // The "Innhold" field's API key.
'TNsfRzsrRSKXhu6rpg8DGg' // ID of the "Min konto" record of the "modular_page" model
});
console.log(parent);
}
run();
By providing the parent_id of the āKundeserviceā record of that model, you tell the API you want to create a new record under it.
Then your āInnholdā field is a single-link field with the API key site_structure_content. By providing the record ID of the āMin kontoā record in the other model, you tell the API you want to create a link to that other record.
I can clean it up myself. But one more question now when everything works: Is there any way to delete all of the structure under one node? I.e. delete all subnodes under āKundeserviceā. It would be nice when developing and testing
Itās not a built-in we have, but you can use something like this:
import {buildClient} from "@datocms/cma-client-node";
const token = process.env.DATOCMS_API_TOKEN; // In env var. Needs CMA write access.
const parentRecordId = 'TenAl6DZSOmoJdWbrZZ3rA'; // The record to start deletion from
async function run() {
const client = buildClient({apiToken: token});
// Get parent record by ID
const parentRecord = await client.items.find(parentRecordId)
// Get model ID from response
const modelId = parentRecord.item_type.id;
// Empty placeholder
let allRecordsOfModel = [];
// Get all records of that model with a listPagedIterator: https://www.datocms.com/docs/content-management-api/resources/item/instances#all_pages
// We need to download them all for clientside processing because we can't retrieve its children automatically
for await (const record of client.items.listPagedIterator({
filter: {
type: modelId
}
})) {
allRecordsOfModel.push(record);
}
// From all records, look for the uppermost parent (the root) then construct a tree of its children
const tree = buildTreeFromRoot(allRecordsOfModel, parentRecordId);
// Also make a flat list of records to delete. It's not technically necessary for this to be a separate step,
// but constructing a tree beforehand is clearer (and can be reused for other operations).
const recordsToDelete = flattenTreeIds(tree)
console.log(`Deleting ${recordsToDelete.length} records starting from '${parentRecordId}'...`);
// Destroy all the records in bulk, up to 200 max.
await client.items.bulkDestroy({items: recordsToDelete.map(id => ({type: 'item', id}))})
}
/****** Helper funcs *******/
/**
* Builds a nested tree structure from a flat array of records.
*
* @param {Array} records - Array of records, each containing at least `id` and optional `parent_id`.
* @param {string} rootId - The ID of the root record to start building the tree from.
* @returns {Object} The nested tree structure starting from the specified root.
*/
const buildTreeFromRoot = (records, rootId) => {
// Step 1: Create a map of records by their ID for quick lookup
const recordById = Object.fromEntries(
records.map((record) => [record.id, record])
);
// Step 2: Create a map that associates each parent ID with its child record IDs
let childrenByParentId = {};
records.forEach((record) => {
const parentId = record.parent_id ? record.parent_id : null; // Use null if no parent
// Initialize the children array for this parent ID if it doesn't exist
if (!childrenByParentId[parentId]) {
childrenByParentId[parentId] = [];
}
// Add the current record's ID to its parent's children array
childrenByParentId[parentId].push(record.id);
});
// Step 3: Recursively build the nested tree
function nestChildren(currentId) {
const currentRecord = recordById[currentId];
// Retrieve the children IDs for the current record, or an empty array if none
const childrenIds = childrenByParentId[currentId] ? childrenByParentId[currentId] : [];
// Build the children array by recursively nesting each child
const children = [];
childrenIds.forEach((childId) => {
const childTree = nestChildren(childId);
children.push(childTree);
});
// Return the current record with its nested children
return {
...currentRecord,
children: children,
};
}
// Step 4: Start building the tree from the root ID
const tree = nestChildren(rootId);
return tree;
}
/**
* Recursively flattens a tree structure into a one-dimensional array of IDs.
*
* @param {Object} tree - The tree to extract IDs from
*
* @returns {string[]} An array containing all `id` values from the root and all child nodes.
*/
const flattenTreeIds = (tree) => {
// If the node has children, recursively flatten them too
const childrenPart = tree.children?.flatMap(flattenTreeIds) ?? [];
// Return an array consisting of the root's ID + all descendant IDs
return [tree.id, ...childrenPart];
}
run();