Script to merge nested child entities as paragraphs into one entity

This script effectively aggregates content from a nested structure of pages into a single page, preserving the hierarchical organization and ensuring that the content from each level is correctly formatted and included. The result is one single page with ‘paragraphs’, maintaining the hierarchical relationship in their position and paragraph header size.

You can define the max depth of merging the nested entities in the script.
The resulting new page is linked to the current (top level) page.
For your own use, replace the const pageDB value with your own space/database names.

Use cases:

  • Project Documentation: Compile detailed documentation spread across multiple sections or items into a single comprehensive document.
  • Weekly Reports: Aggregate weekly progress updates from different team sections into a unified report.
  • Knowledge Base Consolidation: Merge multiple how-to guides and FAQs from various sections into a centralized knowledge base page.
  • Meeting Summaries: Combine notes and outcomes from related meeting items into one summary page for easy reference.
  • Research Aggregation: Collect and combine research findings from several sections into a single, cohesive research document.
  • Product Specifications: Merge detailed specifications and requirements from different product feature sections into a single spec sheet.
  • Educational Content: Combine lesson notes, references, and supplementary material spread across sections into structured course content pages.




const fibery = context.getService('fibery');
const pageDb = 'YourSpaceName/Page'; // The database for Page

// Easily configurable variable for setting maximum levels of sub-pages to merge
const maxDepth = 5; // Set this to the desired maximum depth. Level 0 is the top-level page.

// Function to fetch a page by ID and its related fields
async function fetchPageById(pageId) {
    return await fibery.getEntityById(pageDb, pageId, ['Description', 'Name', 'Sub Pages', 'Parent Page', 'Weight']);

// Recursive function to build the nested content, including titles and body texts
async function buildNestedContent(pageId, depth = 0) {
    // Check if the current depth exceeds the maximum allowed depth
    if (depth > maxDepth) {
        return ''; // Stop processing further sub-pages and return an empty string

    const page = await fetchPageById(pageId);
    // Format the title as a Markdown header, increasing the header level based on depth
    let content = `${'#'.repeat(depth + 2)} ${page.Name}\n\n`;

    // Add the page's description if it exists
    if (page.Description && page.Description.Secret) {
        const descriptionContent = await fibery.getDocumentContent(page.Description.Secret, 'md');
        content += `${descriptionContent}\n\n`;

    if (page['Sub Pages'] && page['Sub Pages'].length > 0) {
        // Fetch full details for sorting, including 'Weight'
        const subPagesDetails = await Promise.all(page['Sub Pages'].map(subPage => fetchPageById(subPage.Id)));

        // Sort sub-pages by their 'Weight' field, treating missing values as zero
        const sortedSubPages = subPagesDetails.sort((a, b) => {
            const weightA = a.Weight !== null && a.Weight !== undefined ? a.Weight : 0;
            const weightB = b.Weight !== null && b.Weight !== undefined ? b.Weight : 0;
            return weightA - weightB;

        for (const subPage of sortedSubPages) {
            // Recursively append content for each sub-page
            content += await buildNestedContent(subPage.Id, depth + 1);
    return content; // Return the compiled content for this page and its sub-pages

async function mergeSubPagesIntoNewSubPage() {
    try {
        const currentEntity = args.currentEntities[0];
        const currentPageEntity = await fetchPageById(;

        // Start building the nested content from the current page
        const nestedContent = await buildNestedContent(;

        // Create a new Sub Page with the merged descriptions
        const timestamp = new Date().toISOString().replace(/:\d+\.\d+Z$/, '');
        const newSubPageTitle = `${currentPageEntity.Name} - Merged ${timestamp}`;

        const newSubPageEntity = await fibery.createEntity(pageDb, {
            'Name': newSubPageTitle,
            'Parent Page': // Link the new Sub Page to the current Page as its parent

        // Update the Description of the new Sub Page with the nested content
        if (newSubPageEntity['Description'] && newSubPageEntity['Description'].Secret) {
            await fibery.setDocumentContent(newSubPageEntity['Description'].Secret, nestedContent, 'md');

        console.log('New Sub Page entity created with nested content, including titles and body texts:', newSubPageEntity);
    } catch (error) {
        console.error('Error in merging Sub Pages into a new Sub Page with nesting:', error);

await mergeSubPagesIntoNewSubPage();


1. Setup and Database Reference

  • Initializes the Fibery service and specifies the database for pages (pageDb).
const fibery = context.getService('fibery');
const pageDb = 'YourSpaceName/Page';

2. Fetch Page Details

  • Defines a function (fetchPageById) to fetch a page by its ID, retrieving its name, description, sub-pages, parent page, and weight.
async function fetchPageById(pageId) { ... }

3. Build Nested Content

  • Implements a recursive function (buildNestedContent) to:
    • Format the page title as a Markdown header based on its depth in the hierarchy.
    • Append the page’s description (if available) fetched from its document content.
    • Recursively process and include content from sub-pages, sorted by their weight, to maintain a structured hierarchy within the content.
async function buildNestedContent(pageId, depth = 0) { ... }

4. Merge and Create New Sub-Page

  • The main function (mergeSubPagesIntoNewSubPage) orchestrates the process:
    • Fetches the current page entity based on the provided currentEntity ID.
    • Builds the nested content starting from the current page, incorporating titles and descriptions from sub-pages up to the specified depth.
    • Creates a new sub-page entity with a title indicating it’s a merged content page, including a timestamp for uniqueness.
    • Updates the new sub-page’s description with the merged content.
    • Logs the creation of the new sub-page or any errors encountered during the process.
async function mergeSubPagesIntoNewSubPage() { ... }

5. Execution

  • Triggers the merging process by calling mergeSubPagesIntoNewSubPage(), which starts the sequence of steps outlined above.
await mergeSubPagesIntoNewSubPage();
1 Like