Blueprint Sync

Blueprints are the source of truth for your data model and the key part of designing your content model. Once you have blueprints created, you can fill in content. All our blueprints, blueprint groups, and blueprint fields have IDs, so you can change the name without losing any content. However, changes to the names of blueprints or fields might break your GraphQL queries in your frontend, so you have to update them. That is why we separated the API name from the title that the editor sees in the CMS.

Get a single Blueprint

Let's start by looking at a blueprint in the CMS UI of a simple blog article.

sample_blueprint_caisy

If you get this whole blueprint with all its groups and fields from the API, it looks like this:

{
    "blueprintId": "d5f7b0fe-3c47-407a-ae35-d6b469059773",
    "groups": [
        {
            "blueprintGroupId": "65927fb5-cbe4-44c5-9140-677769bc01d2",
            "fields": [
                ...
                {
                    "blueprintFieldId": "2de9fa60-a9f1-4464-ad78-eace20aa63e5",
                    "blueprintGroupId": "65927fb5-cbe4-44c5-9140-677769bc01d2",
                    "blueprintId": "d5f7b0fe-3c47-407a-ae35-d6b469059773",
                    "description": "Used to generate the url path",
                    "name": "slug",
                    "options": {
                        "required": true,
                        "string": {
                            "max": 80,
                            "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
                        },
                        "uniqueLocal": true
                    },
                    "title": "slug",
                    "type": "BLUEPRINT_FIELD_TYPE_STRING"
                },
                ...
            ],
            "name": "Main"
        }
    ],
    "name": "BlogArticle",
    "projectId": "85e79eaf-c776-47fa-85f4-564d5bd6410b",
    "tagIds": [],
    "title": "Blog Article",
    "variant": "BLUEPRINT_VARIANT_DOCUMENT"
}

The full example json can be found here.
To get this data, you can either collect all your project blueprints with this GraphQL query from the internal API, as seen here:

SDK
GraphQL
import { initSdk } from "@caisy/sdk";

const token = process.env.CAISY_PERSONAL_ACCESS_TOKEN!;
const projectId = "85e79eaf-c776-47fa-85f4-564d5bd6410b";

const sdk = initSdk({
  token,
});

(async () => {
  const byNameResponse = await sdk.GetBlueprintByName({
    input: { blueprintName: "BlogArticle", projectId },
  });
  const blogArticleBlueprint = byNameResponse.GetBlueprintByName?.blueprint;

  console.log(JSON.stringify(blogArticleBlueprint, null, 2));
})();
{
  GetBlueprintByName(
    input: {blueprintName: "BlogArticle", projectId: "85e79eaf-c776-47fa-85f4-564d5bd6410b"}
  ) {
    blueprint {
      name
      title
      description
      projectId
      blueprintId
      single
      system
      tagIds
      variant
      groups {
        blueprintGroupId
        name
        fields {
          name
          blueprintFieldId
          blueprintGroupId
          blueprintId
          description
          system
          title
          type
        }
      }
    }
  }
}
Blueprints Input and output JSON formats are compatible in Caisy. This means you can get a blueprint JSON, make changes, and update with the same JSON again. System data that is not updated, like the createdAt date, will be omitted automatically by the SDK.

Create a Blueprint

When creating blueprints, all IDs that are not passed are automatically generated. If you are looking into running multiple projects and syncing them, it might be good to note that you can create blueprints with the same IDs in different projects. The same applies for field and group IDs. If you duplicate a project in the UI, the blueprint IDs are also kept identical.

SDK
GraphQL
import { initSdk, BlueprintFieldType, BlueprintVariant } from "@caisy/sdk";

const token = process.env.CAISY_PERSONAL_ACCESS_TOKEN!;
const projectId = "85e79eaf-c776-47fa-85f4-564d5bd6410b";

const sdk = initSdk({
  token,
});

(async () => {
  await sdk.CreateBlueprint({
    input: {
      input: {
        groups: [
          {
            fields: [
              {
                name: "title",
                title: "Title",
                type: BlueprintFieldType.BlueprintFieldTypeString,
              },
            ],
            name: "Main",
          },
        ],
        name: "BlogSection",
        title: "Blog Section",
        variant: BlueprintVariant.BlueprintVariantDocument,
      },
      projectId,
    },
  });
})();
mutation CreateBlueprint($input: CreateBlueprintRequest) {
  CreateBlueprint(input: $input) {
    blueprint {
      name
      title
      description
      projectId
      blueprintId
      single
      system
      tagIds
      variant
      groups {
        blueprintGroupId
        name
        fields {
          name
          blueprintFieldId
          blueprintGroupId
          blueprintId
          description
          system
          title
          type
        }
      }
    }
  }
}

Update a Blueprint

The update operation allows you to modify various aspects of a blueprint, such as its name, description, and the fields within its groups.

One of the advantages of this update process is the ability to move fields between different groups without any problem. As long as the unique identifier (blueprintFieldId) of the field remains the same, you can change the group it belongs to without causing any data loss or having an impact on the external API.

This flexibility allows you to restructure your blueprints as needed, reorganizing fields into different groups or creating new groups altogether. The system ensures that the field data remains intact and correctly associated with its corresponding entity, regardless of which group it resides in.

By maintaining the same blueprintFieldId for each field, the update operation can seamlessly relocate fields within the blueprint structure, ensuring a smooth transition without disrupting any existing data or breaking integrations with external systems.

This approach promotes better organization and maintainability of your blueprints, enabling you to adapt to changing requirements or optimize the structure of your data models without the need for complex data migrations or API changes.

Here's an example of how to retrieve a blueprint and update its fields:

SDK
GraphqQL
import { initSdk } from "@caisy/sdk";

const token = process.env.CAISY_PERSONAL_ACCESS_TOKEN!;
const projectId = "85e79eaf-c776-47fa-85f4-564d5bd6410b";

const sdk = initSdk({
  token,
});

(async () => {
  const blueprintByNameResult = await sdk.GetBlueprintByName({
    input: {
      blueprintName: "BlogSection",
      projectId,
    },
  });

  const blueprint = blueprintByNameResult.GetBlueprintByName?.blueprint;

  const updatedBlueprint = {
    groups: blueprint?.groups?.map((group) => {
      if (group?.fields) {
        group?.fields?.map((field) => {
          // we want to rename the title field to headline
          // be cause we do not touch id of the blueprint field all content will be preserved
          if (field?.name === "title") {
            field.name = "headline";
            field.title = "Headline";
          }

          return field;
        });
      }
      return group;
    }),
    name: blueprint?.name,
    title: blueprint?.title,
    description: blueprint?.description,
    variant: blueprint?.variant,
    exposeMutations: blueprint?.exposeMutations,
    previewImageUrl: blueprint?.previewImageUrl,
    single: blueprint?.single,
    tagIds: blueprint?.tagIds,
  };

  await sdk.UpdateBlueprint({
    input: {
      input: updatedBlueprint,
      blueprintId: blueprint?.blueprintId,
      projectId,
    },
  });
})();
mutation UpdateBlueprint($input: UpdateBlueprintRequest!) {
  UpdateBlueprint(input: $input) {
    blueprint {
      name
      title
      description
      projectId
      blueprintId
      single
      system
      tagIds
      variant
      groups {
        blueprintGroupId
        name
        fields {
          name
          blueprintFieldId
          blueprintGroupId
          blueprintId
          description
          system
          title
          type
        }
      }
    }
  }
}

Delete a Blueprint

Sometimes you may need to remove a blueprint from your project entirely. This operation will permanently delete the blueprint and all its associated data, including groups and fields. Here's an example of how to delete a blueprint:

We can use the DeleteBlueprint mutation to remove a blueprint from our project. This mutation takes the blueprintId as an input parameter, which uniquely identifies the blueprint you want to delete. After executing this mutation successfully, the blueprint and its associated data will be permanently removed from the system.


SDK
GraphQL
import { initSdk } from "@caisy/sdk";

const token = process.env.CAISY_PERSONAL_ACCESS_TOKEN!;
const projectId = "85e79eaf-c776-47fa-85f4-564d5bd6410b";

const sdk = initSdk({
  token,
});

(async () => {
  const blueprintByNameResult = await sdk.GetBlueprintByName({
    input: {
      blueprintName: "BlogSection",
      projectId,
    },
  });

  const blueprint = blueprintByNameResult.GetBlueprintByName?.blueprint;

  await sdk.DeleteBlueprint({
    input: {
      blueprintId: blueprint?.blueprintId,
      projectId,
    },
  });
})();
mutation DeleteBlueprint($input: DeleteBlueprintRequest!) {
  DeleteBlueprint(input: $input) {
    deleted
  }
}

Get all project Blueprints

Retrieving all the blueprints associated with a project can be a useful operation for various reasons. For example, you might want to display a list of available blueprints to users, allowing them to select and work with specific blueprints within your application. Additionally, you may need to perform batch operations or analyses across all blueprints in a project.

Our system supports fetching all the blueprints within a project through a single GraphQL query. This query can be particularly handy when you need to have a comprehensive view of the data models and structures defined across your project.

Here's an example of how to fetch all blueprints from a project:

SDK
GraphQL
import { initSdk } from "@caisy/sdk";
import fs from "fs";
const token = process.env.CAISY_PERSONAL_ACCESS_TOKEN!;
const projectId = "85e79eaf-c776-47fa-85f4-564d5bd6410b";

const sdk = initSdk({
  token,
});


(async () => {
  const getManyBlueprintsResponse = await sdk.GetManyBlueprints({
    input: { projectId },
  });
  const allBlueprints = getManyBlueprintsResponse.GetManyBlueprints?.connection?.edges?.map(edge => edge?.node) || [];

  fs.writeFileSync("allBlueprints.json", JSON.stringify(allBlueprints, null, 2));
})();
{
  GetManyBlueprints(input: {projectId: "85e79eaf-c776-47fa-85f4-564d5bd6410b"}) {
    connection {
      pageInfo {
        hasNextPage
        endCursor
      }
      totalCount
      edges {
        cursor
        node {
          name
          title
          description
          projectId
          blueprintId
          single
          system
          tagIds
          variant
          createdAt
          createdBy
          variant
          groups {
            blueprintGroupId
            name
            fields {
              name
              blueprintFieldId
              blueprintGroupId
              blueprintId
              description
              system
              title
              type
            }
          }
        }
      }
    }
  }
}

The response structure, connection->edges->node follows the Relay specification for pagination and connection handling. The connection field contains the paginated data, with each edge representing a single blueprint. The node field within each edge contains the actual blueprint data.

It's worth noting that for projects with a large number of blueprints (more than 1000), our system supports pagination to ensure efficient data retrieval and prevent potential performance issues. Pagination allows you to fetch blueprints in smaller batches, making it easier to manage and process the data on the client side.

However, for most users and projects, fetching all the blueprints on the first page should be sufficient, as it's uncommon to have thousands of blueprints within a single project. By default, the first page will return the first 1000 blueprints, providing you with a fast overview of the available data models in your project.

Upsert multiple Blueprints

In this example, we'll leverage the output from the GetManyBlueprints query to upsert (insert or update) multiple blueprints in our project simultaneously. This approach allows us to efficiently synchronize our project's blueprints with a backup or external source.

SDK
GraphQL
import { initSdk } from "@caisy/sdk";
import fs from "fs";
const token = process.env.CAISY_PERSONAL_ACCESS_TOKEN!;
const projectId = "85e79eaf-c776-47fa-85f4-564d5bd6410b";

const sdk = initSdk({
  token,
  endpoint: "https://cloud.staging.caisy.io",
});


(async () => {

  const backupFile = fs.readFileSync("allBlueprints.json", "utf-8");
  const backupBlueprints = JSON.parse(backupFile);
  const getManyBlueprintsResponse = await sdk.PutManyBlueprints({
    input: { projectId, blueprintInputs: backupBlueprints },
  });

  console.log(JSON.stringify(getManyBlueprintsResponse, null, 2));
})();
mutation PutManyBlueprints($input: PutManyBlueprintsRequestInput!) {
  PutManyBlueprints(input: $input) {
    successfulBlueprintIds
    errors {
      errorMessage
      blueprintId
    }
  }
}

This approach streamlines the process of synchronizing our project's blueprints with a backup or external source. By directly using the output from GetManyBlueprints as input for PutManyBlueprints, we can efficiently upsert multiple blueprints without the need for manual processing or transformation.

The PutManyBlueprints mutation will create new blueprints for those that don't exist in the project and update existing ones if they have changed, ensuring that our project's blueprints match the desired state defined by the backupBlueprints array.

Bonus: Upsert Blueprints from other project

There are more considerations than just blueprints when duplicating project data. You may also want to copy tags, and potentially other related entities. However, instead of downloading all the data and uploading it again, which can be inefficient, you can leverage a dedicated port operation to copy this data even faster. Here's an example that duplicates tags and blueprints from one project to another, similar to the GetManyBlueprints and PutManyBlueprints operations:

SDK
GraphQL
import { initSdk } from "@caisy/sdk";
const token = process.env.CAISY_PERSONAL_ACCESS_TOKEN!;
const projectId = "85e79eaf-c776-47fa-85f4-564d5bd6410b";

const sdk = initSdk({
  token,
  endpoint: "https://cloud.staging.caisy.io",
});

(async () => {
  const duplicateToProjectResponse = await sdk.DuplicateToProject({
    input: {
      projectId,
      source: {
        projectId: "11e79eaf-c776-47fa-85f4-564d5bd6410a",
      },
      selection: {
        blueprint: true,
        tag: true,
      },
    },
  });
  // this is just if you want to check the progress of the duplication
  // this just takes a couple of 100ms to finish, but still it runs async
  const portId = duplicateToProjectResponse.DuplicateToProject?.portId;

  const portStatusResponse = await sdk.GetProjectPort({
    input: {
      portId,
    },
  });

  console.log(JSON.stringify(portStatusResponse.GetProjectPort, null, 2));
})();
mutation DuplicateToProject($input: DuplicateToProjectRequestInput!) {
  DuplicateToProject(input: $input) {
    portId
  }
}

Conclusion

This article has covered various operations for managing blueprints within your projects using the Caisy Internal API. From creating, updating, and deleting individual blueprints to fetching all blueprints or duplicating entire project structures, the API provides a comprehensive set of tools to streamline your workflow.

As your project grows and evolves, the ability to efficiently manipulate blueprints is crucial. The examples and explanations provided should equip you with the knowledge and skills necessary to seamlessly work with blueprints and ensure your project's data structure remains consistent and up-to-date.

Remember, the power lies in the flexibility and scalability of the Caisy Internal API.

Happy coding, and may your projects thrive with the help of the Caisy Internal API's powerful blueprint management capabilities!