# Access and permissions Source: https://hygraph.com/docs/getting-started/access-and-permissions/access-and-permissions-overview Every user and API client in Hygraph is assigned a set of permissions that controls what they can read, write, and configure. This section covers how to set that up, whether you are onboarding a team member, creating a custom role, or securing an API integration. Hygraph uses three mechanisms to grant permissions: - **Roles** control what authenticated users can do in the project. - **Permanent Auth Tokens (PATs)** control what API clients can do. - **Content API permissions** control what unauthenticated requests to the public Content API can read. All three use the same permission types: content permissions (read, create, update, delete, publish, unpublish) and Management API permissions (schema, environments, settings). Content permissions are environment-specific. Management API permissions apply across all environments in the project. When setting up access for your project, work through these decisions in order: 1. **Who needs access?** Identify everyone who needs access to your project. For each person or system, determine whether they should be granted access through a role, a PAT, or the public Content API settings. 2. **What can they do?** Define the content permissions (which models, locales, and content stages) and any Management API permissions relevant to their role or token. 3. **On which environments?** Content permissions are environment-specific, so you configure them separately per environment. Management API permissions apply globally across all environments in the project. ## Who is this section for? - Project admins managing users and environments - Developers securing API access for applications ### Manage your team - [Manage team members](/docs/getting-started/access-and-permissions/manage-team-members): Invite collaborators to your project, assign roles, and manage access as your team grows. - [User roles and permissions](/docs/getting-started/access-and-permissions/user-roles-and-permissions): Understand system roles, create custom roles, and define what users can read, write, publish, or manage. ### Configure API access - [API access](/docs/getting-started/access-and-permissions/api-access): Configure public Content API access, create Permanent Auth Tokens (PATs), and authenticate API requests. - [Content API permissions](/docs/getting-started/access-and-permissions/content-api-permissions): Reference for content permission rules, including model-level, locale, and content stage configuration. - [Management API permissions](/docs/getting-started/access-and-permissions/management-api-permissions): Reference for permissions that control UI visibility for roles and programmatic access for PATs across schema, environments, and project settings. --- # API access Source: https://hygraph.com/docs/getting-started/access-and-permissions/api-access The **API Access** section of your project settings is where you find your API endpoints, configure unauthenticated Content API permissions, and manage Permanent Auth Tokens (PATs). Navigate to **Project Settings > Access** to get started. For a conceptual overview of authorization and authentication in Hygraph, see [Authorization](/docs/api-reference/basics/authorization). ## Endpoints The **Endpoints** section lists the API URLs for your project's environments. Click any URL to copy it to your clipboard. | Endpoint | Description | |----------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | High Performance Content API | Low-latency, high-read-throughput endpoint for content delivery. | | MCP Server API | Enables structured communication between AI assistants and Hygraph via the Model Context Protocol. See [setting up Hygraph's MCP server](/docs/hygraph-ai/mcp-server). | | Management API | Handles all structural elements of a project. Use it via the [Management SDK](/docs/api-reference/management-sdk/management-sdk). | ## Content API The **Content API** section lets you configure access permissions for unauthenticated requests to your project's Content API, that is, requests made without a PAT. ![API Access - Content API](/images/docs/user-guides/api-access/api-access-publicapi.png) ### Default stage for public content delivery This setting determines which content stage is served when no stage parameter is included in a query or HTTP header. To change it: 1. Click **Change default stage** next to the current stage tag. 2. Select a stage from the available options. 3. Click **Change** to save. [Learn more about the default public stage](/docs/api-reference/basics/authorization#default-public-stage). ### Content permissions Content permissions determine what unauthenticated users can read from your Content API. This section lets you view, add, edit, and delete permissions. ![API Access - Content permissions](/images/docs/user-guides/api-access/content-permissions.png) For a full explanation of how content permissions work, see [Content permissions](/docs/getting-started/access-and-permissions/content-api-permissions). #### Add a permission Click **+ Add permission** at the top right of the permissions table, then follow the [Add content permissions flow](/docs/getting-started/access-and-permissions/user-roles-and-permissions#configure-content-permissions). #### Edit a permission Click the context menu to the left of a permission row and select **Edit**. A dialog will appear where you can update the locale or stage. ![API Access - Edit permissions](/images/docs/user-guides/api-access/edit-permissions.png) #### Delete a permission Click the context menu to the left of a permission row and select **Delete**. Confirm the deletion in the dialog that appears. This action is permanent and cannot be undone. ![API Access - Delete permissions](/images/docs/user-guides/api-access/delete-permissions.png) ## Permanent Auth Tokens Permanent Auth Tokens (PATs) provide authenticated access to the Content API and Management API using Bearer token authentication. Each token can be configured with its own set of content and Management API permissions. The tokens table lists all existing tokens for the project. Click the copy icon to the right of a token to copy it to your clipboard. See [Authorization — Permanent auth tokens](/docs/api-reference/basics/authorization#permanent-auth-tokens) for a conceptual overview of how PATs work. ![API Access - Permanent Auth Tokens](/images/docs/user-guides/api-access/api-access-pat.png) ### Add a token 1. Click **+ Add token** at the top of the tokens table. 2. Enter a name and optional description for the token. 3. Select a default stage for content delivery using the radio buttons. 4. Click **Add & configure permissions** to create the token. The token details screen opens, where you configure content API access and Management API permissions. ### Configure Content API permissions When a PAT is created, default content permissions are enabled: **Read** access on all models for all locales with `PUBLISHED` as the default content stage. From the token detail view you can: - Change the default content delivery stage by clicking **Change default stage** and selecting a different option. - Add a custom permission by clicking **+ Add permission** and following the [Add content permissions flow](/docs/getting-started/access-and-permissions/user-roles-and-permissions#configure-content-permissions). ### Configure Management API permissions To configure Management API permissions for a token: 1. On the token details screen, scroll to the **Management API Permissions** section. 2. Click **Show all permissions** to view the full list. Only enabled permissions are shown by default. 3. Use the toggles to enable or disable individual permissions. 4. To bulk-enable or bulk-disable permissions, use the checkboxes to select multiple rows. The **Enable selected** or **Disable selected** action appears at the top of the table. For a full reference of all available permissions, see [Management API permissions](/docs/getting-started/access-and-permissions/management-api-permissions). ### Edit a token Click a token row in the table, or select **Edit** from its context menu. The token detail view will open, where you can update permissions as described above. ![API Access - Edit token](/images/docs/user-guides/api-access/edit-token.png) ### Delete a token Select **Delete** from the token's context menu, or click **Delete** from within the token detail view. Confirm the deletion in the dialog that appears. Deleting a token is permanent and invalidates all JWTs (JSON Web Tokens) associated with it. ![API Access - Delete token](/images/docs/user-guides/api-access/delete-token.png) ### Can’t find your token in Hygraph? If you copied a PAT a while ago and can’t find the same token string in Hygraph today, the token may still be active even if it looks different. Hygraph uses JWTs (JSON Web Tokens) for Permanent Auth Tokens (PATs). In cases such as audience updates or issuer migrations, the encoded JWT string may change while still representing the same underlying token. Even after such changes, the token remains valid as long as the `jti` (JWT ID) claim within the token's payload remains unchanged. The `jti` claim serves as the authoritative identifier for the token. To verify whether two tokens refer to the same underlying token, follow these steps: 1. Go to https://jwt.io and paste the token that you want to check. This token is no longer visible in Hygraph Studio. 2. Decode the token and locate the `jti` claim in the payload. 3. In Hygraph Studio, go to **Project settings > Access > Permanent Auth Tokens** and check the `jti` values for the tokens listed there. 4. Compare the `jti` values. If the `jti` value found in Step 2 matches the `jti` of a token available in Hygraph Studio, it is the same underlying Permanent Auth Token, even if the encoded JWT differs. The token is still active and has not been revoked. You can then decide whether to keep it or revoke it. - If you want to keep the token, you can replace the deleted JWT with the one that matches from Hygraph Studio. - If you want to revoke the token, you can delete it from Hygraph Studio. This action invalidates all JWTs associated with that token, including any previously issued ones. ![Permanent Auth Tokens list](/images/docs/user-guides/api-access/api-access-tokens.png) ## What's next - [Content permissions](/docs/getting-started/access-and-permissions/content-api-permissions): How content permissions work and their limits. - [Roles and permissions](/docs/getting-started/access-and-permissions/user-roles-and-permissions): Create and configure roles, assign permissions, and set up role-based access. - [Authorization](/docs/api-reference/basics/authorization): Public API permissions, PATs, and endpoints. --- # Content API permissions Source: https://hygraph.com/docs/getting-started/access-and-permissions/content-api-permissions Content permissions control who can read, create, update, delete, publish, and unpublish content in your Hygraph project. You can configure permissions for unauthenticated Public API access, individual Permanent Auth Tokens (PATs), and custom roles. Content permissions are environment-specific. Their configuration is applied per environment. If you are working with multiple environments, you must configure permissions separately for each one. ## Permission actions The permission system is built on seven action types. Granting an action gives the Public API, a PAT, or a custom role permissions to perform that action on all models or a specific model. | Action | Description | Required permissions | |-----------------|-------------------------------------------|----------------------------------------------------------------------------| | `Read` | Read content entries. | — | | `Read versions` | View version history for content entries. | — | | `Create` | Create new content entries. | `Read` on Draft stage and default locale, and `Create` | | `Update` | Modify existing content entries. | `Read` on Draft stage, and `Update` | | `Delete` | Delete content entries. | `Read` on all stages, `Delete`, and `Unpublish` on all stages except Draft | | `Publish` | Publish content entries. | `Read` on Draft stage, and `Publish` on Draft and target stage | | `Unpublish` | Unpublish content entries. | `Read` on all stages, and `Unpublish` on source stage | The **Read versions** action grants access to the version history for a given entry. Custom roles that interact with content via the UI typically need this permission, since versioning is shown as part of the content editor form. ## Set up Public API access The Public API is read-only. To expose content publicly via the API: 1. Navigate to **Project Settings > Access > Content API**. 2. In the **Content Permissions** box, click **Initialize defaults**. This sets `Read` permissions on all models for the **PUBLISHED** stage. 3. If you need custom rules instead, click **+ Add permission**, select **All models**, check **Read**, leave **Locales** and **Stages** at their defaults, and click **Create**. The Public API is now accessible with read permissions. ![Public API Read Permissions](/images/docs/api-reference/basics/public-api-read.png) [Learn more about working with the Public API](/docs/api-reference/basics/authorization). ## Set up a PAT with model-specific permissions A [Permanent Auth Token (PAT)](/docs/api-reference/basics/authorization#permanent-auth-tokens) can be scoped to specific models and actions. The example below configures a PAT that can read, create, and update entries in a `Post` model only. 1. Navigate to **Project Settings > Access > Permanent Auth Tokens** and click **+ Add Token**. 2. Enter a token name and optional description, then click **Add & configure permissions**. 3. Under **Content API** in the token detail view, click **Add permissions**. 4. Select the **Post** model and check **Read**, **Create**, and **Update**. Leave **Locales**, **Stages**, and **Condition** at their defaults, then click **Create**. The token can now read, create, and update `Post` entries. With this configuration, the token cannot access related models such as `Author`, `Asset`, or `SEO`. To connect posts to those models, add separate permissions for each. ![PAT Post Permissions](/images/docs/api-reference/basics/pat-post-permissions.png) [Learn more about authenticating with a PAT](/docs/api-reference/basics/authorization). ## Use conditions Conditions let you restrict a permission to a subset of content entries. This section covers the syntax and constraints for writing conditions. For a walkthrough of how to add a condition to a role in the Hygraph UI, see [Using conditions in role setup](/docs/getting-started/access-and-permissions/user-roles-and-permissions#using-conditions). To define a condition, write a `where` clause in JSON and paste it into the **Condition** field when creating or editing a permission for a specific model. Build and test your `where` clause in the API Playground before adding it as a condition. The following examples show conditions applied to a `Post` model: - Grant access only to posts tagged with specific values: ```json { "tags_contains_some": ["GraphQL", "SEO"] } ``` - Extend the above to also include posts with no tags: ```json { "OR": [{ "tags_contains_some": ["GraphQL", "SEO"] }, { "tags": null }] } ``` ![PAT Post Tag Conditions](/images/docs/api-reference/basics/pat-post-tag-conditions.png) - Restrict access to posts related to a specific author by ID: ```json { "author": { "id_in": ["ckadqgca800ix011230ailipe"] } } ``` For this to work, the token or role also needs read access on the `Author` model. You can optionally restrict that access to a single document: ```json { "id": "ckadqgca800ix011230ailipe" } ``` ![PAT Post Author Conditions](/images/docs/api-reference/basics/pat-post-author-conditions.png) ## Limits and considerations #### Total permissions per environment You can configure up to 50 content permissions in a project environment. You can distribute these across the Public API, PATs, and custom roles as needed. [Learn more about working with environments](/docs/api-reference/basics/environments). #### User system model and user attribution Depending on what you need to expose, you may need to include `Read` access to the `User` system model. This is especially important for custom roles that interact with the UI, as user attribution fields (`createdBy`, `updatedBy`, and `publishedBy`) will not display without the appropriate permissions. Missing these permissions can also cause `not allowed` errors when mutating content from the content editor. See [System fields — User fields](/docs/api-reference/schema/system-fields#user-fields) for more detail. #### Custom roles Custom roles have no permissions by default. At a minimum, a custom role needs: - **Read** access on the `User` system model (required for user attribution to work in the UI) - **Read versions** (required for versioning to display correctly in the content editor) #### Conditions Conditions must be kept up to date manually. If a field referenced in a condition is renamed or removed, or a related model changes, the condition will no longer be valid. The same applies when conditions reference specific document IDs. Conditions cannot be applied to localized fields and do not support `search` capabilities. #### Relations When permissions are set on a model that has relations, permissions may be required on both models. For example, in a schema with `Post` and `Author` models, updating a `Post` to connect it to an `Author` also requires update permissions on the `Author` model, since an author can reference many posts. [Learn more about relations](/docs/api-reference/schema/field-types#reference). #### Locales Permissions support locale-specific configuration. To create or update a document with non-localized fields, the user or token must have access to the default locale. ## What's next - [Authorization](/docs/api-reference/basics/authorization): Public API permissions, PATs, and endpoints. - [Roles and permissions](/docs/getting-started/access-and-permissions/user-roles-and-permissions): Create and configure roles, assign permissions, and set up role-based access. - [API access](/docs/getting-started/access-and-permissions/api-access): Manage API endpoints, Permanent Auth Tokens, and Content API permissions. --- # Manage team members Source: https://hygraph.com/docs/getting-started/access-and-permissions/manage-team-members The **Members** screen gives you an overview of everyone on your project team and the roles they are assigned to. From here you can invite new members, change role assignments, remove members, and navigate to role details. To access this screen, navigate to **Project Settings > Team > Members**. ![Project team members](/images/docs/user-guides/project-guides/projectsettings-team-members.png) ## Invite team members You must invite a user to the project before you can assign them to a role. 1. Click **Invite members** at the top right of the **Members** screen. 2. Enter the user's **Email address** and select their **Role** from the dropdown. The dropdown lists all roles currently configured in your project. [Learn more about roles and permissions](/docs/getting-started/access-and-permissions/user-roles-and-permissions). 3. Click **+ Add to list** to add the user to the invite queue. Repeat for additional members if needed. 4. Click **Invite** to send the invitation. ![Add team members](/images/docs/user-guides/project-guides/projectsettings-team-members-add.png) ### Resending invitations For accounts not yet registered in Hygraph, wait at least 15 minutes before resending an invitation to the same email address. You can resend to the same address a maximum of three times within a 24-hour period. ## Change a member's role 1. Navigate to **Project Settings > Team > Members** and locate the user in the list. 2. Open the context menu for that user and select **Change role**. 3. The role list for that member becomes editable. Select a role from the list to add it. 4. Click **Update** to save the changes. ![Remove a team member from a role](/images/docs/user-guides/roles-and-permissions/change-role.png) ## Remove a member's role 1. Navigate to **Project Settings > Team > Members** and locate the user in the list. 2. Open the context menu for that user and select **Change role**. 3. The role list for that member becomes editable. Click the **×** next to a role name to remove it. 4. Click **Update** to save the changes. ![Remove a team member from a role](/images/docs/user-guides/roles-and-permissions/remove-role.png) ## Remove a member from the project 1. Navigate to **Project Settings > Team > Members** and locate the user in the list. 2. Open the context menu for that user and select **Remove from project**. 3. Confirm the action in the dialog that appears. This action is permanent and cannot be undone. The user will lose access to the project immediately. ![Remove team member from a project](/images/docs/user-guides/project-guides/team-member-remove-from-project.png) ## View role details To view the permissions associated with a role that a member belongs to: 1. Open the context menu for the team member. 2. Select **View role**. If the member has multiple roles, select the specific role you want to inspect. This takes you to the [Roles & Permissions](/docs/getting-started/access-and-permissions/user-roles-and-permissions) screen for that role, where you can see the list of assigned members and the content and Management API permissions configured for it. ![View role](/images/docs/user-guides/project-guides/team-member-view-role.png) ## What's next - [Roles and permissions](/docs/getting-started/access-and-permissions/user-roles-and-permissions): Create and configure roles, assign permissions, and set up role-based access. - [API access](/docs/getting-started/access-and-permissions/api-access): Manage API endpoints, Permanent Auth Tokens, and Content API permissions. --- # Management API permissions Source: https://hygraph.com/docs/getting-started/access-and-permissions/management-api-permissions Management API permissions control what users assigned to a role can see in the Hygraph UI, and what actions Permanent Auth Tokens (PATs) can perform through the Management API. The same set of permissions is available for both [custom roles](/docs/getting-started/access-and-permissions/user-roles-and-permissions) and [Permanent Auth Tokens (PATs)](/docs/getting-started/access-and-permissions/api-access#permanent-auth-tokens), but their defaults and behaviors differ. There are two types of Management API permissions: - **View permissions** control UI visibility. Granting or revoking these shows or hides buttons, tabs, and sections in the interface for users assigned to that role. For example, the `Create new entries` permission controls whether the **+ Add entry** button appears in the content editor. It does not grant the ability to create entries through the API. - **Action permissions** control what can be done via the Management API, such as creating models, updating fields, or managing webhooks. This distinction is especially important for PATs. Granting a UI-only permission to a token has no effect, since tokens do not interact with the UI. Content creation through the API requires the appropriate Content API permissions, not Management API permissions. Management API permissions are **global**. They apply across all environments in a project. ## Roles vs PATs: a key distinction Understanding how Management API permissions behave differently for roles and PATs will save you a lot of confusion when configuring access. - **Roles** - Management API permissions primarily control **UI visibility**. Granting a permission shows a button, tab, or section to users assigned to that role. Revoking it hides it. If a user lacks a permission, the effect is visible in the interface. They may not see a button, get an error when accessing a restricted area, or in some cases be logged out. - Defaults cover the read permissions needed to navigate the Hygraph UI correctly. Anything beyond that must be added manually. - **PATs** - Management API permissions control **API access**. If a token lacks a permission, the API call fails with an insufficient permissions error. There is no UI involved. - Defaults cover `create` and `read` permissions suited for programmatic API access. This distinction matters because some permissions are **UI-only**. They control what users see in the Hygraph interface but have no effect on what a PAT can do programmatically. The most common example is **Create new entries**, which shows or hides the **+ Add entry** button in the content editor. Granting this to a PAT does nothing, because PATs do not interact with the UI and content creation is handled by the Content API, not the Management API. Permissions marked as **UI-only** in the tables below have no effect on PAT behavior. For PATs, content operations such as creating, updating, publishing, and deleting entries should be configured under the token's [content API permissions](/docs/getting-started/access-and-permissions/api-access#configure-content-api-permissions). ## How they differ from content permissions Content permissions govern access to your project's content entries (read, create, update, delete, publish, unpublish). Management API permissions govern access to your project's structural and configuration elements, such as schema, environments, roles, webhooks, and so on. ## Roles Custom roles are an enterprise feature. Contact the sales team to access them. [Hygraph provides five system roles](/docs/getting-started/access-and-permissions/user-roles-and-permissions#system-roles), Owner, Admin, Developer, Editor, and Contributor, each with a predefined permission set that reflects its responsibilities. For example, an Editor cannot access the API Playground, while an Admin or Developer can. Every custom role is assigned a set of default Management API permissions on creation. These defaults cover the minimum needed to use the Hygraph UI correctly. All additional permissions can be added manually depending on what the role needs to do. Note that granting a role additional Management API permissions controls UI visibility only. If users also have Content API permissions granting a particular action, they can still perform that action via the API even if the corresponding UI button is hidden. You can grant a role full content permissions while using Management API permissions to hide specific actions in the UI. Users assigned that role can still perform those actions by calling the API directly. **Content** and **Assets** tabs in Studio are governed by the following Management API permissions: - **Content**: **Read public view groups** and **Read public content views** (not in defaults). Sidebar views also need **Read on DRAFT** per model via [content permissions](/docs/getting-started/access-and-permissions/content-api-permissions#permission-actions). - **Assets**: **Read public view groups** and **Read public content views** + **Read** content permission on the **Asset** model. Assets does not appear without access to the **Content** tab. - **Tables / entries**: Content permissions per model and stage. ### Roles - Project & Studio access | Permission name | Role description | Custom role default | |-----------------|------------------|------------------------| | Change the name, picture and description of a project | Allows users to edit project details and manage support access in **Project Settings > General > Project**. Without this permission, the section is visible but read-only and grayed out. Included by default for the **Admin** and **Developer** system roles. | No | | Can use the playground | Without this permission, users cannot see the **API Playground** in the top-level menu, or the **Preview in playground** option in the content editor and Assets. | No | | Read audit logs | Without this permission, users cannot see the **Audit logs** tab in **Project Settings**. | No | | Can see Team Member Settings | Without this permission, the user cannot see the **Members** tab in **Project Settings > Team**. | No | | Can see Role & Permissions Settings | Without this permission, the user cannot see the **Roles** menu in project settings. | No | | Can see schema view | Allows the user to see the schema editor. Deselect to hide the schema view for roles where it is not relevant. | No | | Can see project settings | Without this permission, users cannot see **Project Settings** in the Studio sidebar or open project settings screens. | Yes | | Can see environment selector | Without this permission, users cannot see the environment selector in Studio. | Yes | ### Roles - Models, components, fields | Permission name | Role description | Custom role default | |-----------------|------------------|------------------------| | Create new models | Without this permission, a user with **`Can see schema view`** can access the schema editor but cannot use the **+ Add** button to create new models. | Yes | | Read existing models | Without this permission, a user with **`Can see schema view`** can access the schema editor but cannot see models in it. | Yes | | Update existing models | Grants users the ability to edit the **Settings** tab of models. Users also need permission to read the schema and models. | No | | Delete existing models | Without this permission, the user cannot see the **Delete** option for models. | No | | Create new components | Without this permission, a user with **`Can see schema view`** can access the schema editor but cannot use the **+ Add** button to create new components. | Yes | | Read existing components | Without this permission, a user with **`Can see schema view`** can access the schema editor but cannot see components in it. | Yes | | Update existing components | Grants users the ability to edit the **Settings** tab of components in the schema editor. Requires **`Can see schema view`**, **Read existing components**, and other read access as needed. Controls schema-level component metadata only. | No | | Delete existing components | Without this permission, the user cannot see the **Delete** option in a component's context menu. | No | | Read existing fields | Without this permission, the user cannot see or access the **Fields** tab in the UI. | Yes | | Create new fields | Without this permission, the user cannot see the **Fields** side panel in the schema editor. | Yes | | Update existing fields | Allows users to see the **Edit** button on field cards in models and edit field details. Users must also have permission to read the schema and models. Without this permission, the **Edit field** button is hidden and the drag-and-drop anchor is not displayed. | No | | Delete existing fields | Allows users to delete fields from a model in the schema editor. Users must also have permission to read the schema and models. | No | ### Roles - Enumerations | Permission name | Role description | Custom role default | |-----------------|------------------|------------------------| | Read existing enumerations | Without this permission, a user with **`Can see schema view`** can access the schema editor but cannot see enumerations in it. | Yes | | Create new enumerations | Without this permission, a user with **`Can see schema view`** can access the schema editor but cannot use the **+ Add** button to create new enumerations. | Yes | | Update existing enumerations | Grants users the ability to edit enumeration details. Users also need permission to read the schema and enumerations. | No | | Delete existing enumerations | Without this permission, the **Delete** option is hidden in the enumeration details context menu. | No | ### Roles - Taxonomies | Permission name | Role description | Custom role default | |-----------------|------------------|------------------------| | Read taxonomy | With **`Can see schema view`**, required to open taxonomies and view taxonomy details (including the **Settings** tab). Without it, users cannot access taxonomy definition screens in the schema area. | Yes | | Create taxonomy | Without this permission, users with **`Can see schema view`** cannot create new taxonomies. Opening existing taxonomies depends on **Read taxonomy** and schema access. Creating a taxonomy typically also requires **Create taxonomy node** for **+ Add** flows. | Yes | | Update taxonomy | Without this permission, the taxonomy Settings page is read-only. | No | | Delete taxonomy | Without this permission, the **Delete** option is hidden in the taxonomy details context menu. | No | | Read taxonomy node | With **`Can see schema view`**, required to browse taxonomy **Nodes** and view node-related detail. Without it, exploring taxonomy nodes is not available. | Yes | | Create taxonomy node | Allows users to use **+ Add child node** (or equivalent). Together with **Create taxonomy**, enables **+ Add** to create a taxonomy where the UI exposes it. | Yes | | Update taxonomy node | Allows users to add new child nodes, rename nodes, or move nodes to a different parent. Without this permission, users can only view taxonomy node details. | No | | Delete taxonomy node | Without this permission, the **Delete** option is hidden next to the taxonomy node. | No | ### Roles - Remote sources | Permission name | Role description | Custom role default | |-----------------|------------------|------------------------| | Read remote sources | Without this permission, a user with **`Can see schema view`** can access the schema editor but cannot see remote sources in it. | Yes | | Create remote sources | Without this permission, a user with **`Can see schema view`** can access the schema editor but cannot use the **+ Add** flow to create new remote sources. | Yes | | Update remote sources | Grants users the ability to edit the **Settings** tab of remote sources. Without this permission, the **Save** button is hidden on the remote source settings screen. | No | | Delete remote sources | Without this permission, users cannot see the **Delete** option for remote sources. | No | ### Roles - Content views | Permission name | Role description | Custom role default | |-----------------|------------------|------------------------| | Create public content views | Without this permission, the **Add custom view** button is hidden. | No | | Read public content views | Required together with **Read public view groups** to access the **Content** tab in Studio. To see views listed in the sidebar, assign [Read content permissions on the DRAFT stage](/docs/getting-started/access-and-permissions/content-api-permissions#permission-actions) on models. For **Assets**, also grant **Read** on the Asset model via [content permissions](/docs/getting-started/access-and-permissions/api-access#configure-content-api-permissions). | No | | Update public content views | Without this permission, the user cannot see the **Edit custom view** option in the custom view context menu. | No | | Update system content views | Without this permission, the user cannot see the **Update default view** option in the content editor and Assets. | No | | Delete public content views | Without this permission, the **Delete custom view** option is hidden in the custom view context menu. | No | ### Roles - View groups | Permission name | Role description | Custom role default | |-----------------|------------------|------------------------| | Create public view groups | Grants users the ability to create view groups. | No | | Read public view groups | Required together with **Read public content views** to access the **Content** tab in Studio. To see views listed in the sidebar, assign [Read content permissions on the DRAFT stage](/docs/getting-started/access-and-permissions/content-api-permissions#permission-actions) on models. For **Assets**, also grant **Read** on the Asset model via [content permissions](/docs/getting-started/access-and-permissions/api-access#configure-content-api-permissions). | No | | Update public view groups | Without this permission, view groups cannot be updated. | No | | Delete public view groups | Without this permission, users will be logged out when attempting to delete a public view group. | No | ### Roles - Locales & stages | Permission name | Role description | Custom role default | |-----------------|------------------|------------------------| | Read locales | Without this permission, users receive an error when trying to access locale information. | Yes | | Create locales | Without this permission, users cannot use the **Add** button in **Project Settings > General > Locales**. | No | | Update locales | Without this permission, locales in **Project Settings > General > Locales** are read-only and grayed out. | No | | Delete locales | Without this permission, the **Delete** button is hidden in **Project Settings > General > Locales**. | No | | Read stages | Required to access content stage information. | Yes | | Create stages | Allows users to see the **+ Add stage** button in **Project Settings > General > Content Stages**. | No | | Update stages | Without this permission, the user cannot see the **Content stages** option in project settings. | No | | Delete stages | Without this permission, clicking **Delete** in **Project Settings > General > Content Stages** throws an error. | No | ### Roles - Environments | Permission name | Role description | Custom role default | |-----------------|------------------|------------------------| | Read existing environments | Without this permission, users cannot access the project and will receive an error when attempting to log in. | Yes | | Create new environment | Without this permission, the **Clone** button in **Project Settings > General > Environments** is disabled. | No | | Update an existing environment | Grants the ability to edit environments through the Management API and related Studio controls. | No | | Delete an existing environment | Without this permission, the **Delete** option in **Project Settings > General > Environments** is disabled. | No | | Promote an existing environment | Without this permission, the **Promote to master** button in **Project Settings > General > Environments** is disabled. | No | ### Roles - Environment backups | Permission name | Role description | Custom role default | |-----------------|------------------|------------------------| | Create new environment backup | Allows creating a new environment backup. | No | | Read existing environment backups and their details | Allows reading existing environment backups and their details. | No | | Update an existing environment backup | Allows updating an existing environment backup. | No | | Delete an existing environment backup | Allows deleting an existing environment backup. | No | | Restore an existing environment backup to a standard environment | Allows restoring an existing environment backup to a standard environment. | No | ### Roles - Content freeze | Permission name | Role description | Custom role default | |-----------------|------------------|------------------------| | Allows starting, scheduling, and lifting content freezes | Without this permission, users cannot access **Project Settings > Governance > Content Freeze**, use the **Manage freeze** link in the Studio banner, or start, schedule, or lift content freezes. | No | ### Roles - Content permission administration Management permissions that control whether a user can manage **content permission** rows in Settings (not document Read/Create itself). | Permission name | Role description | Custom role default | |-----------------|------------------|------------------------| | Can create content permissions | Without this permission, users cannot see the **Add permission** option in the Public Content API, roles, and PATs sections of project settings. | No | | Can read content permissions | Without this permission, the user cannot see the **Content permissions** block for roles and PATs in project settings. | No | | Can update content permissions | Without this permission, users cannot see the **Edit** option for content permissions in the Public Content API, roles, and PATs sections of project settings. | No | | Can delete content permissions | Without this permission, users cannot see the **Delete** option for content permissions in the Public Content API, roles, and PATs sections of project settings. | No | ### Roles - Permanent auth tokens | Permission name | Role description | Custom role default | |-----------------|------------------|------------------------| | Can create new permanent auth tokens | Without this permission, the **+ Add Token** button is hidden on **Permanent Auth Tokens** in **Project Settings**. | No | | Can read existing permanent auth tokens | Allows users to see the **Access** section in **Project Settings**. For the **API Playground**, grant with **`Can use the playground`**. | No | | Can update existing permanent auth tokens | Without this permission, the **Edit** option is hidden in the PAT context menu in **Project Settings > Access**. | No | | Can delete existing permanent auth tokens | Without this permission, the **Delete** option is hidden in the PAT context menu in **Project Settings > Access**. | No | ### Roles - Webhooks | Permission name | Role description | Custom role default | |-----------------|------------------|------------------------| | Create new webhooks | Without this permission, the user cannot see the **+ Add webhook** button. | No | | Read existing webhooks | Without this permission, the user cannot see the **Webhooks** tab in the main navigation. | No | | Update existing webhooks | Without this permission, the user cannot see the **Edit** button for webhooks. | No | | Delete an existing webhook | Without this permission, the user cannot see the **Delete** option for webhooks. | No | ### Roles - Workflows | Permission name | Role description | Custom role default | |-----------------|------------------|------------------------| | Read workflow | Required to access workflow information through the Management API and workflow-related UI context. | Yes | | Create a new workflow | Allows creating a new workflow. | No | | Update a workflow | Allows updating an existing workflow. | No | | Delete a workflow | Allows deleting an existing workflow. | No | | Create a new workflow step | Allows creating a new workflow step. | No | | Update a workflow step | Allows updating an existing workflow step. | No | | Delete a workflow step | Allows deleting an existing workflow step. | No | ### Roles - Users & team | Permission name | Role description | Custom role default | |-----------------|------------------|------------------------| | Invite a user into an existing project | Without this permission, the user cannot see the **Invite members** button in **Project Settings > Team > Members**. | No | | Assign a role to a user | Without this permission, the user cannot see the **Assign role** option in **Project Settings > Team > Members**. | No | | Remove a user from an existing project | Grants users the ability to see the **Remove** option for users. Also required to delete agents via **AI Hub → Agents**. | No | ### Roles - User roles | Permission name | Role description | Custom role default | |-----------------|------------------|------------------------| | Create new roles | Without this permission, the user cannot see the option to add a custom role in the UI. | No | | Update existing roles | Without this permission, the user can open all existing roles, but all update-related buttons are hidden and they will receive an error when attempting to edit content permissions. | No | | Delete an existing role | Without this permission, the user cannot see the **Delete** option for custom roles. | No | ### Roles - Experimental, AI, usage | Permission name | Role description | Custom role default | |-----------------|------------------|------------------------| | Allows managing experimental features and their role assignments | Allows managing experimental features. | No | | Create AI guidelines | Allows creating a new AI guideline. | No | | Read AI guidelines | Allows reading existing AI guidelines. | No | | Update AI guidelines | Allows updating an existing AI guideline. | No | | Delete AI guidelines | Allows deleting an existing AI guideline. | No | | Read observability data | Allows reading observability data. | No | ### Roles - Agents **Invite a new agent**, **Remove an agent**, and **Delete an agent config** appear in Roles & Permissions but have no effect today. You can leave them disabled. To delete an agent, use Delete on the agent card in **AI Hub → Agents**. This requires the [**Remove a user from an existing project**](#roles-users-and-team) permission. | Permission name | Role description | Custom role default | |-----------------|------------------|------------------------| | Create a new agent config | Without this permission, users cannot use **+ Add agent** in **AI Hub → Agents**. | No | | Read agent config | Without this permission, users cannot open **AI Hub → Agents** or agent KPIs when the agents feature is enabled. | No | | Update an agent config | Without this permission, users cannot edit or enable/disable agents in **AI Hub → Agents**. | No | | Trigger an agent run | Without this permission, users cannot manually trigger agents from the content editor or content table. Does not apply to PATs or workflow-triggered runs. | No | ### Roles - Integrations & extensions | Permission name | Role description | Custom role default | |-----------------|------------------|------------------------| | Can add new integrations to an existing project | Allows creating a new integration. | No | | Can see existing integrations in an existing project | Allows reading existing integrations. | No | | Can update existing integrations in an existing project | Allows updating an existing integration. | No | | Can delete existing integrations in an existing project | Allows deleting an existing integration. | No | | Can trigger a netlify build for an existing integration | Allows triggering a build for a Netlify integration. | No | | Can add new extension to an existing project | Allows creating a new extension. | No | | Can see existing extensions in an existing project | Allows reading existing extensions. | No | | Can update existing extensions in an existing project | Allows updating an existing extension. | No | | Can delete existing extensions in an existing project | Allows deleting an existing extension. | No | ### Roles - App installations | Permission name | Role description | Custom role default | |-----------------|------------------|------------------------| | Can add app installations | Without this permission, the **Explore apps** banner is hidden in the **Apps** section. Projects where this permission is not granted do not appear in the project selector dropdown for new app installations. | No | | Can update app installations | Grants the ability to see the **Edit** button on app cards. | No | | Can delete app installations | Without this permission, the user cannot see the **Uninstall** option in the app card context menu. | No | ### Roles - Legacy Management API content permissions | Permission name | Role description | Alternative | |-----------------|------------------|---------------| | Read existing entries | Deprecated. **Does not** control **Content** / **Assets** tabs. | **Read public view groups** + **Read public content views**; **Assets** needs Asset model **Read** via [content permissions](/docs/getting-started/access-and-permissions/api-access#configure-content-api-permissions). | | Create new entries | Affects **+ Add entry** in Studio. Does not replace Content API **Create**. | [Content API permissions — Create](/docs/getting-started/access-and-permissions/content-api-permissions#permission-actions) | | Delete existing entries | **Delete** button visibility in Studio. | [Content API permissions — Delete](/docs/getting-started/access-and-permissions/content-api-permissions#permission-actions) | | Publish non-published entries | Publish from draft where Studio checks this flag. | [Content API permissions — Publish](/docs/getting-started/access-and-permissions/content-api-permissions#permission-actions) | | Update existing non published entries | **Save** on draft / non-published where wired. | [Content API permissions — Update](/docs/getting-started/access-and-permissions/content-api-permissions#permission-actions) | | Update published entries | **Save** when published; unpublish where wired. | [Content API permissions — Update or Unpublish](/docs/getting-started/access-and-permissions/content-api-permissions#permission-actions) | ## Permanent Auth Tokens PATs interact with the Management API programmatically. When you initialize default permissions for a new PAT, those defaults cover the common schema read and create operations needed to work with the [Management SDK](/docs/api-reference/management-sdk/management-sdk). If you need to update or delete schema elements, add those permissions manually. **UI-only permissions have no effect on PATs.** Tokens do not interact with the Hygraph interface, so granting a UI-only permission to a token changes nothing. Content creation through the API requires the appropriate Content API permissions, not Management API permissions. If you need a token to create content, configure the appropriate [content API permissions](/docs/getting-started/access-and-permissions/api-access#configure-content-api-permissions) instead. ### PATs - Project & studio access | Permission name | Action | Description | PAT default | |-----------------|--------|-------------|---------------| | Change the name, picture and description of a project | `PROJECT_UPDATE` | Required for project metadata mutations (name, picture, description, support access). | No | | Can use the playground | `PLAYGROUND_USE` | Studio-only for members with a role; not used to authorize typical PAT Management API traffic. | No | | Read audit logs | `AUDIT_LOGS_READ` | Studio-only for members with a role; not used to authorize typical PAT Management API traffic. | No | | Can see Team Member Settings | `VIEW_TEAM_MEMBER_SETTINGS` | Studio-only for members with a role; not used to authorize typical PAT Management API traffic. | No | | Can see Role & Permissions Settings | `VIEW_ROLE_PERMISSION_SETTINGS` | Studio-only for members with a role; not used to authorize typical PAT Management API traffic. | No | | Can see schema view | `VIEW_SCHEMA` | Studio-only for members with a role; not used to authorize typical PAT Management API traffic. | No | | Can see project settings | `VIEW_PROJECT_SETTINGS` | Studio-only for members with a role; not used to authorize typical PAT Management API traffic. Included in PAT **Initialize defaults**. | Yes | | Can see environment selector | `VIEW_ENVIRONMENT_SELECTOR` | Studio-only for members with a role; not used to authorize typical PAT Management API traffic. | No | ### PATs - Models, components, fields | Permission name | Action | Description | PAT default | |-----------------|--------|-------------|---------------| | Create new models | `MODEL_CREATE` | Required for model-creation mutations (for example `createModel`, `createSimpleModel`). | Yes | | Read existing models | `MODEL_READ` | Required to read models and for most schema mutations that reference existing models. | Yes | | Update existing models | `MODEL_UPDATE` | Required for model update mutations. | No | | Delete existing models | `MODEL_DELETE` | Required for model deletion mutations. | No | | Create new components | `COMPONENT_CREATE` | Required for component-creation mutations. | Yes | | Read existing components | `COMPONENT_READ` | Required to read components and for component-related schema operations. | Yes | | Update existing components | `COMPONENT_UPDATE` | Required for component update mutations (schema components, not content entries). | No | | Delete existing components | `COMPONENT_DELETE` | Required for component deletion mutations. | No | | Read existing fields | `FIELD_READ` | Required to read fields and for field-related schema operations. | Yes | | Create new fields | `FIELD_CREATE` | Required for field-creation mutations on models and components. | Yes | | Update existing fields | `FIELD_UPDATE` | Required for field update mutations. | No | | Delete existing fields | `FIELD_DELETE` | Required for field deletion mutations. | No | ### PATs - Enumerations | Permission name | Action | Description | PAT default | |-----------------|--------|-------------|---------------| | Read existing enumerations | `ENUMERATION_READ` | Required to read enumerations and for enumeration-related schema operations. | Yes | | Create new enumerations | `ENUMERATION_CREATE` | Required for enumeration-creation mutations. | Yes | | Update existing enumerations | `ENUMERATION_UPDATE` | Required for enumeration update mutations. | No | | Delete existing enumerations | `ENUMERATION_DELETE` | Required for enumeration deletion mutations. | No | ### PATs - Taxonomies | Permission name | Action | Description | PAT default | |-----------------|--------|-------------|---------------| | Read taxonomy | `TAXONOMY_READ` | Required for taxonomy queries and taxonomy-related mutations. Not included in PAT **Initialize defaults**. | No | | Create taxonomy | `TAXONOMY_CREATE` | Required for taxonomy creation mutations. | No | | Update taxonomy | `TAXONOMY_UPDATE` | Required for taxonomy update mutations. | No | | Delete taxonomy | `TAXONOMY_DELETE` | Required for taxonomy deletion mutations. | No | | Read taxonomy node | `TAXONOMY_NODE_READ` | Required for taxonomy node queries and node-related mutations. | No | | Create taxonomy node | `TAXONOMY_NODE_CREATE` | Required for taxonomy node creation mutations. | No | | Update taxonomy node | `TAXONOMY_NODE_UPDATE` | Required for taxonomy node update mutations. | No | | Delete taxonomy node | `TAXONOMY_NODE_DELETE` | Required for taxonomy node deletion mutations. | No | ### PATs - Remote sources | Permission name | Action | Description | PAT default | |-----------------|--------|-------------|---------------| | Read remote sources | `REMOTE_SOURCE_READ` | Required to read remote sources and for remote-source-related schema operations. | Yes | | Create remote sources | `REMOTE_SOURCE_CREATE` | Required for remote-source-creation mutations. | Yes | | Update remote sources | `REMOTE_SOURCE_UPDATE` | Required for remote source update mutations. | No | | Delete remote sources | `REMOTE_SOURCE_DELETE` | Required for remote source deletion mutations. | No | ### PATs - Content views | Permission name | Action | Description | PAT default | |-----------------|--------|-------------|---------------| | Create public content views | `CONTENTVIEW_CREATE` | Required for custom content-view creation mutations. | No | | Read public content views | `CONTENTVIEW_READ` | Required to read content views (`environment.contentView`, `environment.contentViews`). | No | | Update public content views | `CONTENTVIEW_UPDATE` | Required to update **custom** content views. | No | | Update system content views | `CONTENTVIEW_SYSTEM_UPDATE` | Required to update **system / default** content views. | No | | Delete public content views | `CONTENTVIEW_DELETE` | Required to delete **custom** content views. | No | ### PATs - View groups | Permission name | Action | Description | PAT default | |-----------------|--------|-------------|---------------| | Create public view groups | `VIEW_GROUP_CREATE` | Required for view-group creation mutations. | No | | Read public view groups | `VIEW_GROUP_READ` | Required to read view groups (`environment.viewGroups` and related fields). | No | | Update public view groups | `VIEW_GROUP_UPDATE` | Required for view-group update mutations. | No | | Delete public view groups | `VIEW_GROUP_DELETE` | Required for view-group deletion mutations. | No | ### PATs - Locales & stages | Permission name | Action | Description | PAT default | |-----------------|--------|-------------|---------------| | Read locales | `LOCALE_READ` | Required for locale queries and locale-dependent Management API operations. | Yes | | Create locales | `LOCALE_CREATE` | Required for locale creation mutations. | No | | Update locales | `LOCALE_UPDATE` | Required for locale update mutations. | No | | Delete locales | `LOCALE_DELETE` | Required for locale deletion mutations. | No | | Read stages | `STAGE_READ` | Required for stage queries and content-stage configuration via the Management API. | Yes | | Create stages | `STAGE_CREATE` | Required for content-stage creation mutations. | No | | Update stages | `STAGE_UPDATE` | Required for content-stage update mutations. | No | | Delete stages | `STAGE_DELETE` | Required for content-stage deletion mutations. | No | ### PATs - Environments | Permission name | Action | Description | PAT default | |-----------------|--------|-------------|---------------| | Read existing environments | `ENVIRONMENT_READ` | Required for environment-scoped queries and mutations for the PAT's environment. | Yes | | Create new environment | `ENVIRONMENT_CREATE` | Required for environment clone/create mutations. | No | | Update an existing environment | `ENVIRONMENT_UPDATE` | Required for environment update mutations. | No | | Delete an existing environment | `ENVIRONMENT_DELETE` | Required for environment deletion mutations. | No | | Promote an existing environment | `ENVIRONMENT_PROMOTE` | Required for promote-to-master (and similar promote) mutations. Restoring from backup uses `ENVIRONMENT_BACKUP_RESTORE`, not this action. | No | ### PATs - Environment backups | Permission name | Action | Description | PAT default | |-----------------|--------|-------------|---------------| | Create new environment backup | `ENVIRONMENT_BACKUP_CREATE` | Required for environment backup creation mutations. | No | | Read existing environment backups and their details | `ENVIRONMENT_BACKUP_READ` | Required to query environment backups and their metadata. | No | | Update an existing environment backup | `ENVIRONMENT_BACKUP_UPDATE` | Required for environment backup update mutations. | No | | Delete an existing environment backup | `ENVIRONMENT_BACKUP_DELETE` | Required for environment backup deletion mutations. | No | | Restore an existing environment backup to a standard environment | `ENVIRONMENT_BACKUP_RESTORE` | Required to restore a backup into a standard environment. | No | ### PATs - Content freeze | Permission name | Action | Description | PAT default | |-----------------|--------|-------------|---------------| | Allows starting, scheduling, and lifting content freezes | `MANAGE_CONTENT_FREEZE` | Required for `startContentFreeze` and `liftContentFreeze` mutations. | No | ### PATs - Content permission administration | Permission name | Action | Description | PAT default | |-----------------|--------|-------------|---------------| | Can create content permissions | `CONTENT_PERMISSION_CREATE` | Required to create content-permission rows (roles, PATs, or public Content API target) via the Management API. | No | | Can read content permissions | `CONTENT_PERMISSION_READ` | Required to read content-permission configuration via the Management API. | No | | Can update content permissions | `CONTENT_PERMISSION_UPDATE` | Required to update content-permission rows via the Management API. | No | | Can delete content permissions | `CONTENT_PERMISSION_DELETE` | Required to delete content-permission rows via the Management API. | No | ### PATs - Permanent auth tokens | Permission name | Action | Description | PAT default | |-----------------|--------|-------------|---------------| | Can create new permanent auth tokens | `PAT_CREATE` | Required for PAT creation mutations. Usually granted on a **custom role** for admins; grant on a PAT only when automation manages other PATs. | No | | Can read existing permanent auth tokens | `PAT_READ` | Required for PAT read/list operations. Usually granted on a **custom role** for admins. | No | | Can update existing permanent auth tokens | `PAT_UPDATE` | Required for PAT update mutations. Usually granted on a **custom role** for admins. | No | | Can delete existing permanent auth tokens | `PAT_DELETE` | Required for PAT deletion mutations. Usually granted on a **custom role** for admins. | No | ### PATs - Webhooks | Permission name | Action | Description | PAT default | |-----------------|--------|-------------|---------------| | Create new webhooks | `WEBHOOK_CREATE` | Required for webhook creation mutations. | No | | Read existing webhooks | `WEBHOOK_READ` | Required for webhook queries. Studio navigation is controlled by custom roles. | No | | Update existing webhooks | `WEBHOOK_UPDATE` | Required for webhook update mutations. | No | | Delete an existing webhook | `WEBHOOK_DELETE` | Required for webhook deletion mutations. | No | ### PATs - Workflows | Permission name | Action | Description | PAT default | |-----------------|--------|-------------|---------------| | Read workflow | `WORKFLOW_READ` | Required for workflow queries and workflow-related Management API operations. | Yes | | Create a new workflow | `WORKFLOW_CREATE` | Required for workflow creation mutations. | No | | Update a workflow | `WORKFLOW_UPDATE` | Required for workflow update mutations. | No | | Delete a workflow | `WORKFLOW_DELETE` | Required for workflow deletion mutations. | No | | Create a new workflow step | `WORKFLOW_STEP_CREATE` | Required for workflow step creation mutations. | No | | Update a workflow step | `WORKFLOW_STEP_UPDATE` | Required for workflow step update mutations. | No | | Delete a workflow step | `WORKFLOW_STEP_DELETE` | Required for workflow step deletion mutations. | No | ### PATs - Users & team | Permission name | Action | Description | PAT default | |-----------------|--------|-------------|---------------| | Invite a user into an existing project | `USER_INVITE` | Required for project member invite mutations. Studio UI is controlled by custom roles. | No | | Assign a role to a user | `USER_ASSIGNROLE` | Required for role-assignment mutations on project members. | No | | Remove a user from an existing project | `USER_REMOVE` | Required for removing members from a project via the Management API. Also required for `deleteAgent` mutations. | No | ### PATs - User roles | Permission name | Action | Description | PAT default | |-----------------|--------|-------------|---------------| | Create new roles | `ROLE_CREATE` | Required for custom role creation mutations. Studio UI is controlled by custom roles. | No | | Update existing roles | `ROLE_UPDATE` | Required for role update mutations (including management and content permissions on roles). | No | | Delete an existing role | `ROLE_DELETE` | Required for custom role deletion mutations. | No | ### PATs - Experimental, AI, usage | Permission name | Action | Description | PAT default | |-----------------|--------|-------------|---------------| | Allows managing experimental features and their role assignments | `MANAGE_EXPERIMENTAL_FEATURES` | Required for experimental-feature management mutations. | No | | Create AI guidelines | `AI_GUIDELINE_CREATE` | Required for AI guideline creation mutations. | No | | Read AI guidelines | `AI_GUIDELINE_READ` | Required for AI guideline queries. | No | | Update AI guidelines | `AI_GUIDELINE_UPDATE` | Required for AI guideline update mutations. | No | | Delete AI guidelines | `AI_GUIDELINE_DELETE` | Required for AI guideline deletion mutations. | No | | Read observability data | `OBSERVABILITY_READ` | Required for observability read operations exposed by the Management API. | No | ### PATs - Agents **Invite a new agent**, **Remove an agent**, **Delete an agent config**, and **Trigger an agent run** can be assigned to a PAT but have no effect on Management API access today. | Permission name | Action | Description | PAT default | |-----------------|--------|-------------|---------------| | Create a new agent config | `AGENT_CONFIG_CREATE` | Required for `createAgent` mutations. | No | | Read agent config | `AGENT_CONFIG_READ` | Required for agent configuration queries. | No | | Update an agent config | `AGENT_CONFIG_UPDATE` | Required for `updateAgent` mutations. | No | ### PATs - Integrations & extensions | Permission name | Action | Description | PAT default | |-----------------|--------|-------------|---------------| | Can add new integrations to an existing project | `INTEGRATION_CREATE` | Required for integration creation mutations. | No | | Can see existing integrations in an existing project | `INTEGRATION_READ` | Required for integration queries. | No | | Can update existing integrations in an existing project | `INTEGRATION_UPDATE` | Required for integration update mutations. | No | | Can delete existing integrations in an existing project | `INTEGRATION_DELETE` | Required for integration deletion mutations. | No | | Can trigger a netlify build for an existing integration | `NETLIFY_TRIGGER_BUILD` | Required for Netlify build trigger mutations on integrations. | No | | Can add new extension to an existing project | `EXTENSION_CREATE` | Required for extension creation mutations. | No | | Can see existing extensions in an existing project | `EXTENSION_READ` | Required for extension queries. | No | | Can update existing extensions in an existing project | `EXTENSION_UPDATE` | Required for extension update mutations. | No | | Can delete existing extensions in an existing project | `EXTENSION_DELETE` | Required for extension deletion mutations. | No | ### PATs - App installations | Permission name | Action | Description | PAT default | |-----------------|--------|-------------|---------------| | Can add app installations | `APP_INSTALLATION_CREATE` | Required for app installation mutations. Studio banners and selectors are controlled by custom roles. | No | | Can update app installations | `APP_INSTALLATION_UPDATE` | Required for app installation update mutations. | No | | Can delete app installations | `APP_INSTALLATION_DELETE` | Required for app uninstall mutations. | No | ### PATs - Legacy Management API content permissions | Permission name | Action | Description | Alternative | |-----------------|--------|-------------|-----------------| | Read existing entries | `CONTENT_READ` | Deprecated. No effect on Content API reads for PAT-authenticated requests. | [Content API permissions — Read](/docs/getting-started/access-and-permissions/content-api-permissions#permission-actions) on the PAT | | Create new entries | `CONTENT_CREATE` | Deprecated. Does not grant Content API **Create** for the PAT. | [Content API permissions — Create](/docs/getting-started/access-and-permissions/content-api-permissions#permission-actions) | | Delete existing entries | `CONTENT_DELETE` | Deprecated. Does not grant Content API **Delete** for the PAT. | [Content API permissions — Delete](/docs/getting-started/access-and-permissions/content-api-permissions#permission-actions) | | Publish non-published entries | `CONTENT_PUBLISH` | Deprecated. Does not grant Content API **Publish** for the PAT. | [Content API permissions — Publish](/docs/getting-started/access-and-permissions/content-api-permissions#permission-actions) | | Update existing non published entries | `CONTENT_UPDATE` | Deprecated. Does not grant Content API **Update** for the PAT. | [Content API permissions — Update](/docs/getting-started/access-and-permissions/content-api-permissions#permission-actions) | | Update published entries | `CONTENT_UPDATE_PUBLISHED` | Deprecated. Does not grant Content API **Update** or **Unpublish** for the PAT. | [Content API permissions — Update or Unpublish](/docs/getting-started/access-and-permissions/content-api-permissions#permission-actions) | --- # User roles and permissions Source: https://hygraph.com/docs/getting-started/access-and-permissions/user-roles-and-permissions Roles determine what users can see and do in your Hygraph project. Each role carries a set of **content permissions**, which govern access to content entries, and **Management API permissions**, which determine what a user can do in the UI and through the API. **Content permissions are environment-specific.** Their configuration is applied per environment. If you are working with multiple environments, configure content permissions separately for each one. **Management API permissions are global.** They apply across all environments in a project. Hygraph provides five built-in system roles and supports custom roles for projects on enterprise plans. To access roles and permissions, navigate to **Project Settings > Team > Roles & Permissions**. ![Roles and Permissions overview](/images/docs/user-guides/roles-and-permissions/roles-and-permissions-overview.png) ## System roles System roles are built into every Hygraph project. They cannot be edited or deleted. | Role | Rights | |-------------|----------------------------------------------------------------------------------------------| | Owner | All Admin rights, plus the ability to change billing settings and delete the project. | | Admin | All Developer rights, plus the ability to manage team members and create or update projects. | | Developer | All Editor rights, plus the ability to create, update, and delete models and enumerations. | | Editor | All Contributor rights, plus the ability to delete content. | | Contributor | Ability to create and update content. | Only **Admins** and **Owners** can view the permissions granted to each role or assign team members to roles. ## Custom roles Custom roles are available on enterprise plans only. Custom roles let you define exactly what a user can see and do, without exposing features that are not relevant to their work. Only project **Admins** and **Owners** can create and manage custom roles. When configuring a custom role, always include the **Read** and **Read versions** content permissions. Without **Read**, users cannot open content entries. Without **Read versions**, versioning will not display correctly in the content editor. ### Create a custom role 1. Click **+ Add custom role** at the top right of the roles screen. 2. Enter a **Name** for the role and optionally a **Description**. 3. Click **Create**. ![Add custom role](/images/docs/user-guides/roles-and-permissions/add-custom-role.png) The new role is added to the list with basic read permissions assigned by default. Use **View permissions** from the context menu to configure the full permission set. From the context menu of a custom role, you can: - **View permissions**: View and edit the content and Management API permissions for this role. - **Assign members**: Assign project members to this role. - **Delete role**: Permanently delete this role. ### Configure content permissions Content permissions determine what actions users can perform on content entries in the content editor. ![Add Content permissions](/images/docs/user-guides/roles-and-permissions/add-content-permission.png) 1. Click on the custom role in the roles table to open its permissions screen. 2. Click **+ Add permission**. 3. Use the **Model** dropdown to select the model to apply permissions to. Select **All** to apply them across all models. 4. Use the checkboxes to select the actions to grant. Some actions reveal additional options for **Locales** and **Stages**. 5. Click **Create** to save. | Permission | What it allows | |-----------------|---------------------------------------------------------------------| | `Read` | View content entries. Required as a baseline for all other actions. | | `Create` | Create new content entries. | | `Update` | Edit existing content entries. | | `Delete` | Delete content entries. | | `Publish` | Publish content entries to a stage. | | `Unpublish` | Remove content entries from a published stage. | | `Read versions` | View version history for a content entry. | Use the **Reset** button at the bottom left of the screen to reset permissions to their default state before saving. ### Configure Management API permissions Management API permissions control both API access and UI visibility. 1. Click on the custom role in the roles table. 2. The screen shows only enabled permissions by default. Click **Show all available permissions** to see the full list. - For a complete reference of all available Management API permissions, see the [Management API permissions reference](/docs/getting-started/access-and-permissions/management-api-permissions). 3. Use the toggles to enable or disable individual permissions. 4. Use the checkboxes to select multiple permissions. The **Enable selected** or **Disable selected** bulk action appears at the top of the table. For a **read-only role**, the default permissions are sufficient. No additional Management API permissions are needed. For a **publisher role**, also enable **Update published entries**, **Create new entries**, **Publish non-published entries**, **Update existing non-published entries**, and **Delete existing entries**. ### Delete a custom role System roles cannot be deleted. To delete a custom role: 1. Open the context menu for the role. 2. Select **Delete role**. 3. Confirm the deletion in the popup. This action cannot be undone. ![Delete a custom role](/images/docs/user-guides/roles-and-permissions/delete-role.png) ## Assign members to a role Before assigning a user to a role, they must be invited to the project. See [Manage team members](/docs/getting-started/access-and-permissions/manage-team-members) for instructions. To assign team members to a role: 1. Open the context menu for the role and select **Assign members**. 2. Select one or more team members using the checkboxes. 3. Click **Assign**. If all current project members are already assigned to the role, a popup will let you know. Click **Invite members** in that popup to add new people to the project. ## View role permissions To view all content and Management API permissions for a role, open the context menu and select **View permissions**. From the permissions screen you can: - Sort permissions alphabetically by model or action. - Filter permissions by action, model, locale, and stage. - Assign new members to the role by clicking **Assign members** at the top right. Permissions for system roles are read-only. To edit permissions, use a [custom role](#custom-roles). ## Remove a user To remove a team member from a role or from the project entirely, go to **Project Settings > Team > Members**. See [Manage team members](/docs/getting-started/access-and-permissions/manage-team-members#remove-a-members-role) for full instructions. ## Permission setup examples The following examples show common permission configurations. Use the browser find command (`Ctrl+F` or `Cmd+F`) to locate specific permission names in the Management API permissions reference. ### Read-only Content API permissions: - **Model:** All - **Read:** All locales, all stages - **Read versions:** Enabled No additional Management API permissions are needed. The defaults are sufficient for a read-only role. ![Read-only user](/images/docs/user-guides/roles-and-permissions/setup-example-read-only.png) ### Read and publish Content API permissions: - **Model:** All - **Read:** All locales, all stages - **Publish:** All stages, all destination stages, all locales - **Read versions:** Enabled Additional Management API permissions to enable: - `Update published entries` - `Create new entries` - `Publish non-published entries` - `Update existing non-published entries` - `Delete existing entries` ![Read and publish setup](/images/docs/user-guides/roles-and-permissions/setup-example-read-and-publish.png) ### By model Select the target model from the **Model** dropdown when creating a content permission. Permissions will apply only to entries of that model. ![Setup by models](/images/docs/user-guides/roles-and-permissions/setup-example-models.png) Selecting multiple models at once is not supported, unless you select all models. To configure the same permissions on two different models, create a separate permission for each. If the target model references other models, you must also add at least **Read** and **Update** permissions on each referenced model for actions like create, update, delete, publish, or unpublish to work correctly. ### By locale When selecting a permission action, use the **Locales** dropdown to restrict it to one or more specific locales. ![Setup by locales](/images/docs/user-guides/roles-and-permissions/setup-example-locales.png) You must also grant access to the default locale for any role that needs to create or update content. ### By content stage Use the **From stages** and **To stages** dropdowns to restrict publish and unpublish actions to specific stages. For example, you can limit a role to publishing from **Draft** to a **QA** stage only, preventing direct publishing to **Published**. ![Setup by content stage](/images/docs/user-guides/roles-and-permissions/setup-example-content-stage.png) Check out [Content Workflows](/docs/developer-guides/project/content-workflows) to learn how to use content stages for approval workflows. For help with a specific setup, contact the Hygraph support team. ### By environment Management API permissions apply to all environments in a project. Content permissions are environment-specific, so you can configure different permissions on each environment for the same role. For example, to give a role read-only access on the master environment and publish access on a secondary environment: 1. Switch to your master environment. 2. Set up the role as read-only, as described in the [read-only example above](/docs/getting-started/access-and-permissions/user-roles-and-permissions#read-only). 3. Switch to the secondary environment. [Learn how to switch environments](/docs/developer-guides/project/manage-project-environments#switch-environments). 4. Open the custom role and select **View permissions**. 5. Add the **Publish** and **Unpublish** content permissions. 6. Enable the additional Management API permissions required for publishing, as described in the [read and publish example above](/docs/getting-started/access-and-permissions/user-roles-and-permissions#read-and-publish). ![Setup by environment](/images/docs/user-guides/roles-and-permissions/setup-example-content-stage.png) ### Using conditions Conditions let you restrict a permission to specific content entries. Instead of granting a role access to all entries in a model, you can scope it further using a GraphQL `where` clause. For example, for entries with a particular field value, tag, or ID. To add a condition when configuring a content permission: 1. Open the custom role and click **+ Add permission**. 2. Select the target model from the **Model** dropdown. 3. Check the actions you want to grant. 4. Enter a JSON `where` clause in the **Condition** field. 5. Click **Create** to save. ![Conditions](/images/docs/user-guides/roles-and-permissions/setup-example-conditions.png) Conditions must be maintained manually. If a field is renamed or a referenced document ID changes, the condition becomes invalid. Update affected conditions whenever the underlying schema or content changes. For the full conditions reference including syntax, examples, and constraints, see [Use conditions](/docs/getting-started/access-and-permissions/content-api-permissions#use-conditions) in the content permissions documentation. Conditions require familiarity with GraphQL `where` clauses. Use the API Playground to build and test a condition before applying it to a role. ## What's next - [Manage team members](/docs/getting-started/access-and-permissions/manage-team-members): Invite members and manage project access. - [Content permissions](/docs/getting-started/access-and-permissions/content-api-permissions): Detailed reference for content permission rules, limits, and conditions. - [Management API permissions](/docs/getting-started/access-and-permissions/management-api-permissions): Full reference for all Management API permission options. - [Authorization](/docs/api-reference/basics/authorization): Public API permissions, PATs, and endpoints. --- # Hygraph account settings Source: https://hygraph.com/docs/getting-started/account-settings Account settings let you manage the core details of your Hygraph account, including personal information and notifications. These settings apply across all projects you have access to. At the top right of the screen, click your avatar and select **Account settings**. ![Account settings](/images/docs/getting-started/fundamentals/project-directory-supports-and-settings.png) Account settings apply to your user account. Project-specific configuration is managed separately within each project. ## Profile information Your profile contains basic information associated with your account, such as your name and email address. This information is used across Hygraph for identification and collaboration. Some fields, like your email address, may be managed by your identity provider if your organization uses Single Sign-On (SSO). ## Notifications Use notification settings to choose whether Hygraph notifies you about replies, mentions, assignments, and status updates through email or in-app notifications. You can adjust these preferences at any time to match your workflow. ## Update your password To change your account password, you will need to log out and choose **Forgot your password** on the login page to receive an email for setting a new one. If you've logged in with GitHub or Google, you can change your password there and you'll be logged in automatically with Hygraph. ## Delete your account You can permanently delete your Hygraph account from the account settings page. Deleting your account is irreversible and removes your personal access to Hygraph. Any projects you own will be deleted, while projects you are a member of will remain active for other members. --- # Hygraph overview Source: https://hygraph.com/docs/getting-started/index Hygraph is a GraphQL-native structured content platform, also referred to as a headless CMS, built to model, govern, and deliver content across every brand, channel, and market. This page covers what Hygraph is, how the platform is structured, and what capabilities are available to your team. ## What is Hygraph? Hygraph gives development and content teams a single, governed foundation for structuring, creating, and delivering content programmatically. Content is modeled as structured entities and relationships, not pages, so it can be reused, governed, and delivered anywhere: any frontend, any backend, through one GraphQL API. Content Federation extends that same API to external systems, so data from commerce platforms, PIMs, and third-party APIs is queryable alongside your own content. Your schema lives in one place. Your content can come from anywhere. Your frontend is your choice. ## How it works Hygraph projects follow a consistent structure across three layers. ### Schema You define your content structure by creating models, adding fields, and configuring relationships between them. Models can include reusable components, remote data sources, and sidebar widgets for custom editorial tools. ### Content Once your schema is set, teams create and manage entries in the Content Editor. Entries move through configurable stages (such as `Draft` and `Published`), and you can schedule publishing, bundle entries into releases, and assign collaborative workflows to ensure the right people review content before it goes live. Editors can preview content in context using Live Preview, and make field-level edits directly from a frontend page using Click-to-Edit. ### Delivery Hygraph exposes your content through a globally distributed GraphQL API. You query your own content and federated remote sources in a single request. ## Platform capabilities The sections below map the capabilities available across Hygraph by area. ### Content modeling The **Schema Builder** supports scalar fields, relational fields, union types, enumerations, components, and remote sources. Conditional fields let you show or hide fields in the editor based on values entered elsewhere in the entry, keeping complex models manageable for editorial teams. Use taxonomies to define hierarchical classification structures, such as tags, categories, and facets that apply consistently across content types. Use variants to define audience-specific versions of content entries without duplicating content entries. ### Editorial experience The content editor supports localization, scheduled publishing, release management, and collaborator view, which shows when another user is editing the same entry. Quick filters and Content Finder reduce the time it takes to locate entries across large projects. ### AI capabilities AI Assist lets editors generate, improve, and localize content through natural language prompts inside the entry form. AI Agents automate repetitive editorial tasks, such as translation, SEO analysis, and content summarization, directly inside your Hygraph project. You can trigger an agent manually on selected entries, or configure it to run automatically when an entry moves through a workflow step. When an agent completes its task, the entry enters a read-only review state where the editor can approve or revert each field change before editing resumes. ### Visual editing Live Preview displays a preview of your frontend alongside the content editor, so changes are visible without publishing. With Click-to-Edit, editors can hover over any element in your preview, click **Edit**, and the editor opens at the exact field. ### Asset management The Assets manager handles uploads, transformations, and delivery for images, video, and documents. Asset transformations, such as resizing, format conversion, and cropping, apply at the API level via URL parameters, so you don't need a separate tool to manage image delivery. ### Content Federation Remote sources connect external REST or GraphQL APIs to your Hygraph schema. Once configured, remote data is queryable through the same GraphQL endpoint as your local content. Top-level remote fields let you fetch remote content directly without anchoring it to a Hygraph model. ### Developer tools Hygraph provides a Content API for content delivery, a Management SDK for programmatic schema and content operations, and Webhooks for event-driven integrations. The App Framework lets you build custom field extensions, sidebar elements, and third-party integrations tailored to your project. The MCP Server exposes Hygraph to MCP-compatible AI tools. ### Access and governance Granular permissions let administrators define role-based access at the model, field, stage, and locale level. Content workflows add structured approval steps to content creation, so entries pass through defined checkpoints before publication. Audit logs record all actions, including those performed via app tokens, PATs, and third-party integrations. ## Use cases Hygraph is built for organizations where content spans more than one brand, market, or system. - **Multi-Brand Enterprises**: One governed content foundation for every brand, replacing separate CMS instances per brand. - **eCommerce Across Regions & Languages**: Structured product content, localized and delivered via GraphQL, without duplicating entries per market. - **AI Operations & Agentic Content**: A structured content graph AI agents can reliably consume, connected via MCP within governed boundaries. - **B2B Portals & Audience Targeting**: Role-based content delivery, by account, region, or entitlement, built into the model, not the frontend. ## What's next - [Quickstart](/docs/getting-started/quickstart): Create your first project, define a schema, and query content. - [Studio walkthrough](/docs/getting-started/studio-walkthrough): Get oriented with the Hygraph interface and where key features live. - [eCommerce tutorial](/docs/getting-started/tutorial/tutorial-overview): Build a complete Hygraph project from schema to connected frontend. --- # Migrate to Hygraph Source: https://hygraph.com/docs/getting-started/migrating-to-hygraph Migrating to Hygraph involves two distinct phases: rebuilding your schema, then importing your content. Hygraph gives you the Management SDK and Content API to handle both programmatically, as well as a UI-based option for schema creation. This guide covers the full migration flow, from exploring your existing data to importing content, along with tips for specific field types such as assets, rich text, and relations. ## Prerequisites - An active Hygraph project - A [Permanent Auth Token](/docs/api-reference/basics/authorization#permanent-auth-tokens) with Management API access (for schema creation via the SDK) - A Permanent Auth Token with Content API mutations enabled (for content import) - An export of your existing content in JSON or CSV format ## Step 1: Explore your current data Before creating anything in Hygraph, examine the structure of your existing project and map it to Hygraph's data model. ![Plan your schema](/images/docs/getting-started/fundamentals/plan-your-schema.png) Work through the following questions: - What models and fields do you currently have, and how do they map to [Hygraph's field types](/docs/api-reference/schema/field-types)? - How do your models relate to each other? - Are there structures you want to normalize or improve as part of the migration? - Which data belongs in Hygraph, and which belongs elsewhere? For example, you may want image assets in Hygraph but video assets in a dedicated streaming service. Export your existing content to JSON or CSV so you can inspect field names, content types, and relations. The examples in this guide use the following CSV of authors: ```csv oldId,firstName,lastName 1,Stephen,King 2,Frank,Herbert 3,Brian,Herbert 4,Kevin,Anderson 5,Agatha,Christie 6,Haruki,Murakami 7,Isaac,Asimov ``` Hygraph supports over a dozen [field types](/docs/api-reference/schema/field-types), from strings, booleans, and dates to polymorphic union types and remote field resolvers. You get a small set of [system fields](/docs/api-reference/schema/system-fields) out of the box, but everything else is defined by you. Some teams use migration as an opportunity to restructure their schema, for example, extracting repeated content into components. This can improve efficiency, but may require manual intervention when importing content. If you skip normalization for now, you can use `String` and `JSON` fields to represent most data without modification, though you will lose some filtering capability at the API level. ## Step 2: Create your schema ![Create your schema](/images/docs/getting-started/fundamentals/create-your-schema.png) Create your schema before importing any content. You have two options: the Management SDK or the Hygraph UI. The SDK is faster for large or complex schemas and gives you a repeatable record of what was created. ### Use the Management SDK The [Management SDK](/docs/api-reference/management-sdk/management-sdk) lets you create models, fields, enumerations, components, and remote sources programmatically. All changes are submitted as a single transaction. If any operation fails, the entire batch rolls back automatically. **Install the SDK:** ```bash npm install @hygraph/management-sdk ``` **Initialize the client:** ```ts const { Client } = require('@hygraph/management-sdk'); // endpoint is your High Performance Content API URL // found in Project Settings > Endpoints > High Performance Content API const client = new Client({ authToken, endpoint, name, // optional }); ``` Use `createModel` to create a model: ```js client.createModel({ apiId: '', apiIdPlural: '', description: '', displayName: '', }); ``` To create two models, `Author` and `Book`: ```js client.createModel({ apiId: 'Author', apiIdPlural: 'Authors', displayName: 'Author', }); client.createModel({ apiId: 'Book', apiIdPlural: 'Books', displayName: 'Book', }); ``` Use `createSimpleField` to add fields to a model. The example below shows all available options. Use only the ones you need: ```js client.createSimpleField({ apiId: '', description: '', displayName: '', embeddableModels: '', embedsEnabled: '', formConfig: '', formExtension: '', formRenderer: '', isHidden: '', isList: '', isLocalized: '', isRequired: '', isTitle: '', isUnique: '', migrationValue: '', parentApiId: '', position: '', tableConfig: '', tableExtension: '', tableRenderer: '', type: SimpleFieldType.STRING, validations: '', visibility: '', }); ``` To add a required string field to the `Author` model: ```js client.createSimpleField({ parentApiId: 'Author', type: SimpleFieldType.STRING, apiId: 'favoritePastime', displayName: 'Author Favorite Pastime', isRequired: true, visibility: VisibilityTypes.ReadWrite, }); ``` **Full migration example** The script below creates an `Author` model with `firstName` and `lastName` fields: ```js // migration.js const { Client, SimpleFieldType } = require('@hygraph/management-sdk'); const client = new Client({ authToken: '', endpoint: '', }); // Create the Author model client.createModel({ apiId: 'Author', apiIdPlural: 'Authors', displayName: 'Author', }); // Add firstName field to Author client.createSimpleField({ parentApiId: 'Author', apiId: 'firstName', displayName: 'First Name', type: SimpleFieldType.STRING, }); // Add lastName field to Author client.createSimpleField({ parentApiId: 'Author', apiId: 'lastName', displayName: 'Last Name', type: SimpleFieldType.STRING, }); // Preview all changes before committing const changes = client.dryRun(); console.log(changes); ``` Run the script from the command line: ```bash node migration.js ``` Review the `changes` array to confirm the operations that will be applied. Once you are satisfied, replace `dryRun()` with `run()` to commit the changes: ```js async function runMigration() { const result = await client.run(true); if (result.errors) { throw new Error(result.errors); } console.log(result.name); } runMigration(); ``` Once the migration runs, verify the result by checking the schema editor in Hygraph or introspecting your endpoint. If your schema includes components, enumerations, or remote source fields, create those at the schema level before adding them to models. See the [Management SDK field creation examples](/docs/api-reference/management-sdk/management-sdk-field-examples) for instructions. **Additional resources:** - [Management SDK quickstart](/docs/api-reference/management-sdk/management-sdk-quickstart) - [Management SDK methods reference](/docs/api-reference/management-sdk/management-sdk-methods-reference) - [Management SDK full example](/docs/api-reference/management-sdk/management-sdk-example) - [Batch migrations](/docs/api-reference/management-sdk/management-sdk-batchmigration) ### Use the UI To create your schema in the Hygraph UI, navigate to the schema editor and create your models, then add fields to each one. The [getting started guide](/docs/getting-started/quickstart) covers creating models and adding fields. ## Step 3: Plan your content migration With your schema in place, review your existing content and map it to the new structure before importing anything. Order matters when content is relational. For example, you must create asset entries before creating content entries that reference them, otherwise the relation cannot be established at import time. As you plan, identify: - Which content needs to be migrated first to unblock dependent content - Which content is critical vs. supporting, so you can prioritize accordingly - Where the shape of your existing data differs from your new input types, and what transformation is needed ## Step 4: Import your content Import assets before content entries. Relations cannot be established until the assets they reference exist. How you upload assets depends on which asset system your project uses. ![API Endpoints](/images/docs/getting-started/fundamentals/api-endpoints-location.png) ### Assets Projects created after February 2024 use the Hygraph Asset Management system. Projects older than that use the Legacy asset system. The upload process differs between the two: - **Hygraph Asset Management:** Asset uploads are part of the native GraphQL API. Upload through `createAsset` mutations instead. - **Legacy asset system:** Uses a dedicated HTTP upload endpoint at `/upload` appended to your project URL. To check which system your project uses, navigate to **Project Settings > Access > Endpoints** and look for an asset upload endpoint. If one is listed, your project uses the legacy system. If not, it uses Hygraph Asset Management. You will need a [Permanent Auth Token](/docs/api-reference/basics/authorization#permanent-auth-tokens) with **Mutations** access enabled to upload assets. File size limits depend on your plan. Check the pricing page for details. Assets follow the same [environment](/docs/api-reference/basics/environments) and [authorization](/docs/api-reference/basics/authorization) settings as all other content in your project. After uploading, [publish your assets](/docs/api-reference/assets/publishing-assets) before they can be served alongside published content. For full asset upload documentation, see [Uploading assets](/docs/api-reference/assets/uploading-assets). **Upload by file — Hygraph Asset Management** First, create the asset via mutation to receive the upload URL and credentials: ```graphql mutation createAsset { createAsset(data: {}) { id url upload { status expiresAt error { code message } requestPostData { url date key signature algorithm policy credential securityToken } } } } ``` ```json { "data": { "createAsset": { "id": "clt47n0t600j807vvirlzi1xx", "url": "https://eu-central-1.graphassets.com/clpqzrnm4007e01t810b59ir4/clt47n0t600j907vveibipmov", "upload": { "status": "ASSET_CREATE_PENDING", "expiresAt": "2024-02-27T12:38:49+00:00", "error": null, "requestPostData": { "url": "https://eu-1-assets-delivery-hg75hf.s3.eu-central-1.amazonaws.com", "date": "20240227T101349Z", "key": "clpqzrnm4007e01t810b59ir4/upload/...", "signature": "c17e7b1c5d4af665a8fc74421fae53b72e94bb19e85e7befd1eb79b865bef7d2", "algorithm": "AWS4-HMAC-SHA256", "policy": "eyJleHBpcmF0aW9uIjo...", "credential": "ASIAVQRE3VMEWGY5C2XL/20240227/eu-central-1/s3/aws4_request", "securityToken": "IQoJb3JpZ2luX2Vj..." } } } } } ``` Then upload the file using the credentials from the response: ```bash curl --request POST \ --url $URL \ --form X-Amz-Date=$DATE \ --form key=$KEY \ --form X-Amz-Signature=$SIGNATURE \ --form X-Amz-Algorithm=$ALGORITHM \ --form policy=$POLICY \ --form X-Amz-Credential=$CREDENTIAL \ --form X-Amz-Security-Token=$SECURITY_TOKEN \ --form file=@./test.jpg ``` **Upload by remote URL — Hygraph Asset Management** ```graphql mutation uploadByUrl { createAsset( data: { uploadUrl: "https://images.unsplash.com/photo-1682687218147-9806132dc697" } ) { id url } } ``` **Upload by file — legacy asset system** ```bash curl -XPOST -H "Authorization: Bearer {YOUR_PAT_VALUE}" -F fileUpload=@picture.jpg https://[region].hygraph.com/v2/[projectId]/[environment]/upload ``` ```js // File must use the .mjs extension, as node-fetch is an ESM-only package. import fetch, { FormData, fileFrom } from 'node-fetch'; const form = new FormData(); form.set('fileUpload', await fileFrom('path/to/file.png')); fetch(`${process.env.HYGRAPH_URL}/upload`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.HYGRAPH_ASSET_TOKEN}`, }, body: form, }) .then((res) => res.json()) .then((data) => console.log(JSON.stringify(data, null, 2))) .catch((err) => console.log(err)); ``` ```js const HYGRAPH_URL = ''; const HYGRAPH_ASSET_TOKEN = ''; async function upload() { const input = document.getElementById('fileUpload'); const file = input.files[0]; const form = new FormData(); form.append('fileUpload', file); // Do not expose HYGRAPH_ASSET_TOKEN in front-end code in production. // Use a backend service to handle uploads and keep the token server-side. const response = await fetch(`${HYGRAPH_URL}/upload`, { method: 'POST', headers: { Authorization: `Bearer ${HYGRAPH_ASSET_TOKEN}`, }, body: form, }); const data = await response.json(); console.log(JSON.stringify(data, null, 2)); } ``` ```json { "filename": "pexels-photo-1170986.jpeg", "mimetype": "image/jpeg", "size": 32476, "width": 500, "height": 750, "url": "https://media.graphassets.com/P3TkBzxyQLupgDWNFydB", "id": "ckfdz530o0001ip92cdr3bbmj" } ``` **Upload by remote URL — legacy asset system** ```bash curl -XPOST -d url=https://media.graphassets.com/P3TkBzxyQLupgDWNFydB https://[region].hygraph.com/v2/[projectId]/[environment]/upload ``` ```js // File must use the .mjs extension, as node-fetch is an ESM-only package. import fetch from 'node-fetch'; fetch(`${process.env.HYGRAPH_URL}/upload`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.HYGRAPH_ASSET_TOKEN}`, 'Content-Type': 'application/x-www-form-urlencoded', }, body: `url=${encodeURIComponent( 'https://media.graphassets.com/P3TkBzxyQLupgDWNFydB' )}`, }) .then((res) => res.json()) .then((data) => console.log(JSON.stringify(data, null, 2))) .catch((err) => console.log(err)); ``` ```json { "filename": "pexels-photo-1170986.jpeg", "mimetype": "image/jpeg", "size": 32476, "width": 500, "height": 750, "url": "https://media.graphassets.com/J9BOEF3OSuuSXDhvriQa", "id": "ckgs20b80017109547vfev24c" } ``` ### Content entries To find your Content API endpoint, go to **Project Settings > Access > Endpoints > High Performance Content API**. Use [GraphQL mutations](/docs/api-reference/content-api/mutations#auto-generated-mutations) to create content entries. Hygraph auto-generates mutations for every model you create. Because existing data rarely maps 1:1 to your new schema, you will likely need to transform your dataset to match your new input types before importing. The script below shows a complete import example using the [CSV authors file from Step 1](#step-1-explore-your-current-data): ```js // Import necessary libraries const { GraphQLClient, gql } = require('graphql-request'); const csvToJson = require('csvtojson'); require('dotenv').config(); // Initialize GraphQL client const client = new GraphQLClient(process.env.HYGRAPH_ENDPOINT, { headers: { authorization: `Bearer ${process.env.HYGRAPH_TOKEN}`, }, }); // Build a mutation from a data row function createMutation(data) { return gql` mutation MyMutation { createAuthor(data: { firstName: "${data.firstName}", lastName: "${data.lastName}", oldId: "${data.oldId}" }) { id } } `; } // Run the migration async function run() { // Load and parse the CSV const data = await csvToJson().fromFile('./data.csv'); // Build mutations from each row const mutations = data.map((item) => createMutation(item)); // Execute each mutation with a 1-second delay between requests mutations.forEach((mutation, index) => { setTimeout(() => { console.log(`Running mutation ${index + 1} of ${mutations.length}`); client.request(mutation).then((response) => { console.log(response); }); }, (index + 1) * 1000); }); } run(); ``` See [rate limits](/docs/api-reference/basics/rate-limits) for guidance on request frequency. ### Rich text Hygraph stores rich text as an Abstract Syntax Tree (AST) based on Slate. If your existing content stores rich text as HTML or another format, you need to convert it before importing. 1. **Convert:** Use Hygraph's HTML-to-Slate AST converter to transform your existing rich text into the correct AST format. 2. **Import:** Use a `create` mutation with a `RichTextAST` variable to import the converted content. ```graphql mutation createArticle($title: String, $content: RichTextAST) { createArticle(data: { title: $title, content: $content }) { title content { raw } } } ``` ```json { "title": "Working with the Hygraph Rich Text field", "content": { "children": [ { "type": "paragraph", "children": [ { "text": "Hygraph boasts an impressive collection of " }, { "href": "https://hygraph.com/docs/api-reference/schema/field-types", "type": "link", "children": [{ "text": "Field Types" }] }, { "text": " that you can use when content modeling." } ] } ] } } ``` For additional rich text utilities, see the Hygraph rich text helpers. ### Relations There are two approaches to migrating relational content. **Option 1: Create with nested mutations** Use a [create mutation](/docs/api-reference/content-api/mutations#create-entries) with a nested create to build both sides of a relation in one request. For subsequent entries that share the same related record, use a `connect` mutation instead of creating a duplicate. **Option 2: Create separately, then connect** Create all entries for each model first using [create mutations](/docs/api-reference/content-api/mutations#create-entries), then wire them together using [update mutations](/docs/api-reference/content-api/mutations#update-entries) or [update many mutations](/docs/api-reference/content-api/mutations#update-many). This approach is more straightforward but requires more total mutations. Option 2 requires more mutations to be sent. Review the [rate limits](/docs/api-reference/basics/rate-limits) documentation before choosing your approach. ```graphql # Create a book with a new author (one relation) mutation createOneRelation { createBook( data: { name: "Rose madder" slug: "rose-madder" price: 30 author: { create: { name: "Stephen King", slug: "stephen-king" } } } ) { id name author { name } } } # Create a book with multiple new authors mutation createManyRelations { createBook( data: { name: "The road to Dune" slug: "the-road-to-dune" price: 30 authors: { create: [ { name: "Frank Herbert", slug: "frank-herbert" } { name: "Brian Herbert", slug: "brian-herbert" } { name: "Kevin Anderson", slug: "kevin-anderson" } ] } } ) { id name author { name } } } ``` ```graphql # Create a book and connect to an existing author mutation createAndConnectOne { createBook( data: { name: "Rose madder" slug: "rose-madder" price: 30 author: { connect: { slug: "stephen-king" } } } ) { id name author { name } } } # Create a book and connect to multiple existing authors mutation createAndConnectMany { createBook( data: { name: "The road to Dune" slug: "the-road-to-dune" price: 30 authors: { connect: [ { slug: "frank-herbert" } { slug: "brian-herbert" } { slug: "kevin-anderson" } ] } } ) { id name author { name } } } ``` ## Migration best practices Follow these guidelines to keep your migration predictable and recoverable: - Plan the full migration before you start. Map your existing schema to Hygraph models and fields, and identify any transformations needed. - Use the migration as an opportunity to improve your schema. Hygraph features like components can reduce duplication. Restructure where it makes sense. - Migrate in dependency order. Assets must exist before content entries that reference them. Shared models must exist before models that connect to them. - Prioritize critical content. Identify your most important content and migrate it first, before supporting or supplementary content. - Avoid large, complex mutations. If a model connects to many other models, do not try to create and connect everything in a single mutation. Space requests out and stay within [rate limits](/docs/api-reference/basics/rate-limits). ## What's next - [Management SDK](/docs/api-reference/management-sdk/management-sdk): Full reference for creating and updating schema elements programmatically. - [Content API mutations](/docs/api-reference/content-api/mutations): Reference for create, update, connect, and nested mutations. - [Upload assets](/docs/api-reference/assets/uploading-assets): Full documentation for asset uploads, including both legacy and current asset systems. - [Rate limits](/docs/api-reference/basics/rate-limits): Understand request limits before running large-scale imports. - [Field types](/docs/api-reference/schema/field-types): Reference for all field types available in Hygraph. --- # Quickstart Source: https://hygraph.com/docs/getting-started/quickstart This Quickstart walks you through creating your first Hygraph project, from setup to querying content. If you’re looking for a deeper, end-to-end walkthrough, check out our [Full Tutorial: Build Your First eCommerce Project](/docs/getting-started/tutorial/tutorial-overview). It expands on these concepts with guided, step-by-step lessons that take you from project creation all the way to connecting a frontend. ## Register 1. You'll need a Hygraph account to get started. Go to https://app.hygraph.com/signup. 2. Select one of the available options: - GitHub - Google - Email, password, and name 3. Select the checkbox to agree to the terms of service and privacy policy, and click **Continue**. 4. You'll receive a verification email. Click on the provided link and log in using your credentials. After logging in, you'll land in your project directory. This is where you’ll find all your projects and create new ones. Any pending project invites are available at the top of the screen. If you’d like a quick overview of the Hygraph interface and navigation, see the [Studio walkthrough](/docs/getting-started/studio-walkthrough). ## Create a project When you log in, you'll see a list of projects you own or have been invited to. If you’re new to Hygraph, this list may be empty. You can either create a blank project or use our Showcase project to explore how Hygraph works. Click **Get started** to start exploring the Showcase project. Starting with the Showcase project helps you get a feel for how the platform works. ![First screen](/images/docs/getting-started/fundamentals/first-screen.png) To add a blank project, follow these steps: 1. If you've just signed up, click **Start from scratch**. Otherwise, click **Add project**. 2. Select your plan. Choose Hobby, Enterprise, or start a 30-day trial. See our Pricing page for details. 3. Provide a project name, an optional description, and a content region. If you need a custom region, contact sales. - Hygraph stores your content in the region you select and delivers it globally via our CDN. Learn more in the [Caching](/docs/api-reference/basics/caching) documentation. 4. Click **Add project**. Hygraph redirects you to the project homepage. ![Blank new project](/images/docs/getting-started/fundamentals/new-project-blank.png) ## Create a model Let's create a model for our products. Think of a model as a database table or collection in NoSQL that defines the schema and structure of your content. 1. Open the **Schema** builder. 2. Click **+ Add** and create a new model named `Product`, keeping the suggested API IDs. 3. Optionally add a description for editors and API users. 4. Click **Add Model**. You’ve added a **Product** model to your GraphQL schema. With the model in place, you can now start adding fields. ## Add fields to your model Fields define the data you can store on a model. Each field uses one of Hygraph’s supported [field types](/docs/api-reference/schema/field-types). For this Quickstart, you'll add three fields to the `Product` model: - `Name` - `Price` - `Image` ### Name field 1. Select the **Single line text** field from the sidebar. 2. Set the **Display name** to **Name**. 3. Under **Validations**, enable **Make field required**. 4. Click **Add**. ### Price field 1. Select the **Number** field from the sidebar. 2. Set the **Display name** to **Price**. 3. Under **Validations**, enable **Make field required**. 4. Click **Add**. ### Image field 1. Select the **Asset picker** field from the sidebar. 2. Set the **Display name** to **Image**. 3. Enable **Allow multiple assets**. This lets you add more than one image to each product entry. 4. Click **Add**. Every project includes an **Asset** model by default. It is used to store images and other files. Learn more about [working with assets](/docs/developer-guides/assets/work-with-assets). ### System fields Hygraph automatically manages [system fields](/docs/api-reference/schema/system-fields) such as `id`, `createdAt`, and `publishedAt`. To view system fields, enable the **Show system fields** toggle in the **Fields** tab for your selected model. ![Show system fields](/images/docs/getting-started/fundamentals/show-system-fields-toggle.png) ## Create a content entry With your schema ready, it’s time to add content. ![Add entry](/images/docs/getting-started/fundamentals/add-entry-button.png) 1. Navigate to the **Content** editor in your Hygraph project. 2. Under the **Default views** list, select the **Product** view. If your project only has the **Product** model so far, this view will display automatically when you access the content editor. 3. Your content entries table for **Product** is currently empty. To create content, click **+ Add entry** at the top-right corner of the screen. 4. Enter details for your new product. The available inputs correspond to the fields you added to the model. - Required and multi-value fields are enforced by the schema. Learn more about [field validations](/docs/api-reference/schema/field-configuration#validations). - `Name` and `Price` were set to **required** in the schema, so the UI here reflects those constraints. 5. Now, upload one or more images for your product. Click **Add image**, and upload an asset from your computer. - `Image` allows multiple images to be uploaded since we allowed **multiple values** in the schema. 6. Click **Save** to keep the entry in **DRAFT**. ![Entry in DRAFT stage](/images/docs/getting-started/fundamentals/entry-draft-stage.png) ## Publish your content By default, all projects include the **DRAFT** and **PUBLISHED** [content stages](/docs/developer-guides/content/content-stages). Draft is commonly used for previews or staging environments, while Published represents live content. Publishing promotes an entry from **DRAFT** to **PUBLISHED** so it can be consumed via the API. 1. Click **Publish** at the top-right corner of the screen while editing your content entry to publish it. Once you click **Publish**, a confirmation modal displays. This modal will also include any related entries or assets that are currently in the **DRAFT** stage and may need publishing as well. 2. If you have unpublished referenced entries, select the **All entries** checkbox to include all related entries and assets in the **DRAFT** stage. Then, click **Publish**. 3. You can continue to make changes to your content entry, and then save it again as many times as you want without publishing. When you save but don't publish, it is marked as **outdated**. This means the published version differs from the latest draft in your Hygraph project. ![A blue Published pill indicates that the content entry is outdated](/images/docs/getting-started/fundamentals/outdated-content-entry.png) 4. You can repeat publishing outdated entries either by clicking the **Publish** button again, or by [scheduling for later](/docs/developer-guides/content/scheduled-publishing#how-to-schedule). Before saving changes you made to a content entry, the **Publish** button will read **Save & Publish** instead. Clicking **Save & Publish** saves and publishes the content at once, so make sure you only use it when the content is ready to go live. Check out the document on [content publishing](/docs/developer-guides/content/publishing-content) for more information. ## Query content For every model that you create, Hygraph automatically generates GraphQL queries to fetch content entries. You can test these queries in the [API Playground](/docs/api-reference/basics/api-playground). ![The API Playground](/images/docs/getting-started/fundamentals/api-playground.png) 1. Navigate to the **API Playground** in your Hygraph project. 2. As you type `product` in the API Playground, you'll see recommended queries to fetch single or multiple product entries, an individual product version, and the [connection query](/docs/api-reference/content-api/queries#fetching-with-relay) to fetch edges/nodes. 3. Enter the following query: ```graphql { products { id name price image { url fileName } } } ``` 4. Click the **Play** icon to run the query. - Since we enabled **Allow multiple assets** on the `image` field, the API will return our images within an array (denoted by `[]` in JSON), even if you upload only one image. - If you had unchecked **Allow multiple assets** in the schema, the `image` field would return a single object `{}` instead of an array `[]`. ```json { "data": { "products": [ { "id": "cmkcb1fu3itum07moq2f5pzjj", "name": "My product entry", "price": 2000, "image": [ { "url": "https://eu-west-2.graphassets.com/cmk6vvkkl0dzk07mj5law3czs/cmkci63gk05hv07mmqaf80haj", "fileName": "t-shirt-white.jpg" } ] } ] } } ``` Explore the full [API Reference](/docs/api-reference) for additional information on filtering, pagination, ordering, and asset transformations. Our [Full Tutorial: Build Your First eCommerce Project](/docs/getting-started/tutorial/tutorial-overview) offers the option to clone a project that you can use to practice queries. ## Mutate content For every model that you create, Hygraph automatically generates GraphQL mutations so you can create, update, delete, publish, and unpublish content entries. You can try out all mutations in the [API Playground](/docs/api-reference/basics/api-playground). 1. Navigate to the **API Playground** in your Hygraph project. 2. In the API Playground, you'll start with the following: ```graphql mutation { } ``` 3. Start typing `product` to see mutations for the `Product` model. For this example, we'll use the `updateProduct` mutation to modify the product entry we previously created using the UI. - Hover over the mutation to view its arguments and documentation. - Explore input types such as `ProductUpdateInput` to see available fields. ![updateProduct mutation](/images/docs/getting-started/fundamentals/update-product-mutation.png) 4. Use the `where` and `data` arguments to write the mutation. In this example, you’ll update the `price` field. - You'll need an `id` of the product you created previously to continue. You can copy it from the query results in the [Query content](#query-content) section. ```graphql mutation { updateProduct(where: { id: "" }, data: { price: 25 }) { id name price } } ``` 5. Click the **Play** icon to execute this mutation. The product entry is updated with the new `price` value. Our [Full Tutorial: Build Your First eCommerce Project](/docs/getting-started/tutorial/tutorial-overview) offers the option to clone a project that you can use to practice mutations. ## API access To access your content outside of Hygraph, you need to configure Content API permissions. 1. Navigate to **Project settings > Access > Content API**. 2. To create new permissions, click **Initialize defaults**. This sets `Read` permissions on all models on the **PUBLISHED** stage. This means that anyone can read published content via the Content API. You can also protect the API with [permanent auth tokens](/docs/api-reference/basics/authorization). Learn more in the [API access](/docs/getting-started/access-and-permissions/api-access) documentation. ![Content API permissions](/images/docs/getting-started/fundamentals/public-content-api-permissions.png) 3. Now that the API is public, let's test it. Go to **Project settings > Access > Endpoints**, and copy the **High Performance Content API** endpoint. This is the default Content API used for production reads. ![Content API URL](/images/docs/getting-started/fundamentals/content-api-url.png) 4. Paste this endpoint in your browser. 5. Run the query from the [Query content](/docs/getting-started/quickstart#query-content) section of this document. This is now publicly accessible on the web. - If you see any errors, verify that you've [published at least one `Product` content entry](#publish-your-content). ![Content publicly available](/images/docs/getting-started/fundamentals/content-publicly-available.png) ## Next steps | Document | About | |-------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------| | [Full Tutorial: Build Your First eCommerce Project](/docs/getting-started/tutorial/tutorial-overview) | Build a complete e-commerce project from schema design to a connected frontend. | | [Roles and permissions](/docs/getting-started/access-and-permissions/user-roles-and-permissions) | Information on roles and permissions including system roles, custom roles, how to work with roles & permissions, and detailed examples. | --- # Hygraph Studio walkthrough Source: https://hygraph.com/docs/getting-started/studio-walkthrough This page walks through the Hygraph Studio interface so you know where everything lives before you start building. If you want a step-by-step guide to creating a project and querying content, start with the [Quickstart](/docs/getting-started/quickstart). ## Overview When you log in to your Hygraph account, you land in the project directory. This is the account-level view, separate from any individual project. Here you can find the following tabs at the top: - **Projects** - **Your apps** - **Analytics** ### Projects **Projects** lists your active projects and any pending project invites. It also includes a Showcase project you can use to explore Hygraph features before building your own. ![Projects](/images/docs/getting-started/fundamentals/projects-tab.png) ### Your apps **Your apps** is where you create and manage custom apps you have built using the Hygraph App Framework. Apps you create here can be installed into projects and shared with other users. ![Your apps](/images/docs/getting-started/fundamentals/project-directory-apps-tab.png) ### Analytics **Analytics** gives you a usage overview across all projects you have access to, scoped to the `master` environment. You can customize which projects appear in the list and which columns are shown, including plan, API calls, asset traffic, content entries, locales, and seats. You can perform the following tasks on this dashboard: - Click **Open** next to the project name to access the project. - Select **Adjust columns** to customize which columns you want to display. The following columns are available:
- Owner of the project - Plan - Region - Billing period - API calls - Asset traffic - Content models
- Components - Remote sources - Taxonomies - Content entries - Locales - Seats - Workflows
![Analytics](/images/docs/getting-started/fundamentals/project-analytics.png) ## Project sidebar Once you open a project, the left sidebar is your primary navigation. It contains the following sections. ![Hygraph Studio sidebar](/images/docs/getting-started/fundamentals/project-dashboard.png) ### Home The project homepage. It surfaces recently updated schema elements, links to documentation, and video content. ### Schema The Schema builder is where you define the structure of your content. You create models, add fields, configure relationships, set up components, and connect remote sources here. | Action | Description | |---|---| | [Create a model](/docs/getting-started/quickstart#create-a-model) | Define a new content model and add fields to it. | | [Add fields to a model](/docs/getting-started/quickstart#add-fields-to-your-model) | Extend an existing model with new field types. | | [Add components](/docs/developer-guides/schema/components) | Create reusable field templates that can be embedded in multiple models. | | [Add remote sources](/docs/developer-guides/remote-data/remote-sources) | Connect external APIs so their content is queryable through the Hygraph API. | | [Add enumerations](/docs/developer-guides/schema/using-enumerations) | Define a fixed list of values for use in dropdown fields. | | [Customize the content entry sidebar](/docs/developer-guides/content/customize-sidebar) | Add or remove widgets from the content entry sidebar. | ### Content The Content editor is where you create, edit, review, and publish entries based on the models in your schema. It supports default and custom views, so you can organize your content workspace to match your editorial workflow. | Action | Description | |---|---| | [Create a content entry](/docs/getting-started/quickstart#create-a-content-entry) | Add a new entry based on an existing model. | | [Publish content](/docs/getting-started/quickstart#publish-your-content) | Make an entry available via the Content API for the `PUBLISHED` stage. | | [Update content](/docs/developer-guides/content/updating-content) | Edit an existing entry, whether published or in draft. | | [Duplicate content](/docs/developer-guides/content/duplicating-content) | Copy an entry to use as the basis for a new one. | | [Schedule publishing](/docs/developer-guides/content/scheduled-publishing) | Set a future date and time for an entry to publish or unpublish. | | [Delete content](/docs/developer-guides/content/deleting-content) | Remove entries individually or in bulk. | | [Access content views](/docs/developer-guides/content/access-created-content) | Navigate to and customize how your content list is displayed. | ### Assets The Assets manager is where you upload and store files used in content entries. Like the Content editor, it supports default and custom views. | Action | Description | |---|---| | [Add an asset entry](/docs/developer-guides/assets/add-asset-entry) | Upload a file and add metadata to it. | | [Upload assets](/docs/developer-guides/assets/upload-asset) | Bring files in from different sources. | | [Work with assets](/docs/developer-guides/assets/work-with-assets) | Manage your asset table, similar to working with content entries. | ### AI Hub AI Hub contains Hygraph's AI-related tooling for your project, including AI agents and Guidelines. It is distinct from **AI Assist**, which is the top-bar tool for generating and improving content inline within the Content editor. | Action | Description | |---|---| | [AI agents](/docs/hygraph-ai/ai-agents) | Create and manage AI agents for your project. | | [AI guidelines](/docs/hygraph-ai/ai-guidelines) | Create and manage AI guidelines for your project. | ### API Playground The API Playground lets you test queries and mutations against your project's API directly in Studio. Use it to verify that your schema is returning the data you expect before wiring up a frontend. | Action | Description | |---|---| | [Test queries](/docs/api-reference/content-api/queries) | Run GraphQL queries against your content API. | | [Test mutations](/docs/api-reference/content-api/mutations) | Create, update, or delete content via the API to verify behavior. | ### Apps The **Apps** section shows the apps installed in your current project environment. From here you can configure or uninstall them, or go to the Marketplace to browse and install more. This is different from the **Your apps** tab in the project directory, where you create and manage apps you have built yourself. | Action | Description | |---|---| | [Install apps](/docs/integrations) | Browse and install apps from the Marketplace. | ### Project Settings Project Settings is where you manage everything that applies to the project as a whole: API access, environments, locales, roles, billing, and team members. | Action | Description | |---|---| | [Manage project information](/docs/developer-guides/project/manage-project-info) | Edit project details, clone your project, enable public cloning, manage support access, leave the project, or delete it. | | [Manage environments](/docs/developer-guides/project/manage-project-environments) | Create and switch between isolated instances of your project. | | [Manage API access](/docs/getting-started/access-and-permissions/api-access) | Configure endpoints, content API permissions, and permanent auth tokens. | | [Configure webhooks](/docs/developer-guides/webhooks/webhooks-overview) | Set up webhooks to trigger external actions on content events. | | [Manage roles and permissions](/docs/getting-started/access-and-permissions/user-roles-and-permissions) | Control what different team members can see and do in your project. | | [Manage team members](/docs/getting-started/access-and-permissions/manage-team-members) | Add members and assign roles. | | [Manage locales](/docs/developer-guides/project/manage-project-locales) | Add languages to support localized content. | | [Manage content stages](/docs/developer-guides/content/content-stages) | Create custom workflow stages beyond Draft and Published. | | [Manage audit logs](/docs/developer-guides/project/audit-logs) | Review a full history of schema and content changes in your project. | | [Update billing](/docs/getting-started/update-billing) | Change your plan or update payment information. | | [Clone your project](/docs/developer-guides/project/clone-your-project) | Create a copy of your project's master environment. | | [Share your project](/docs/developer-guides/project/share-your-project) | Generate a public clone link for others to copy your project. | ## Top bar The top bar is visible across all project sections. It gives you access to account-level tools and contextual actions. ![Hygraph Studio top bar](/images/docs/getting-started/fundamentals/top-bar.png) | Item | What you can do | |---|---| | Project and environment | See the current project and environment. Use the dropdown to switch projects or environments. | | Trial indicator | View your current plan. Click to open the plan picker and upgrade. | | Search | Find content entries across your environment. | | Recently viewed | Return to entries you viewed recently. | | AI Assist | Generate, improve, and localize content inline within the content entry editor. Available on enabled projects only. | | Help | Access documentation, code examples, and the Hygraph Slack community. | | Contact Support | Reach the Hygraph support team. | | Notifications | View alerts and updates across all your Hygraph projects. Unread notifications show a red indicator on the bell icon. | | Profile | Access your account settings or log out. | ## What's next - [Quickstart](/docs/getting-started/quickstart): Create a project, define a schema, and query your first content entry. - [Tutorial overview](/docs/getting-started/tutorial/tutorial-overview): Start building a full project in Hygraph, from schema to frontend. --- # Lesson 4.2 - Add components to your models Source: https://hygraph.com/docs/getting-started/tutorial/tutorial-add-components-to-models In this lesson, you will attach the components built in lesson 4.1 to the `Product`, `Landing page`, and `Navigation` models. After this lesson, the schema is structurally complete. ## Product The `Product` model receives two component fields: `Product Variant` and `Related Products` as basic component fields. Both are basic because each field always contains a single, fixed component type — there is no editorial choice about which component fills the field. 1. In the Schema editor, open the `Product` model. 2. From the right sidebar, add a **Basic** component field with the following information, and click **Add** to save: | Field | Input | |---|---| | Display Name | Product Variant | | API ID | ProductVariant | | Select component | Product Variant | 3. Add a second **Basic** component field with the following information, and click **Add** to save: | Field | Input | |---|---| | Display Name | Related Products | | API ID | RelatedProduct | | Description | Add related products | | Select component | Related Products | Your Product model should now look like this: ![Finished Product model with fields](/images/docs/getting-started/product-model.png) ## Landing page The `Landing page` model receives a modular component field called `Stripes`. It is modular because editors need to choose which component fills each section, either a `Call to Action` or a `Product Grid`. Allowing multiple values means editors can add as many sections as needed, in any order. 1. In the Schema editor, open the **Landing page** model. 2. From the right sidebar, add a **Modular** component field with the following information, and click **Add** to save: | Field | Input | |---|---| | Display Name | Stripes | | API ID | stripes | | Description | Sections | | Allow multiple values | Select checkbox | | Select allowed components | Call to Action, Product Grid | Your Landing page model should now look like this: ![Finished Landing Page model with fields](/images/docs/getting-started/landing-page-model.png) ## Navigation The Navigation model was left incomplete in lesson 1.2. It has a Nav ID field but no way to hold navigation links. The Link component built in lesson 4.1 is what completes it. 1. In the Schema editor, open the **Navigation** model. 2. From the right sidebar, add a **Basic** component field with the following information, and click **Add** to save: | Field | Input | |---|---| | Display Name | Nav Link | | API ID | navLink | | Description | Navigation links | | Allow multiple values | Select checkbox | | Select component | Link | The **Allow multiple values** setting is what enables the navigation to hold multiple links. Each link added in the content editor is a separate instance of the Link component. Your `Navigation` model should now look like this: ![Finished Navigation model](/images/docs/getting-started/navigation-model.png) Every model has the fields, references, and component fields it needs. ## What's next Or, go to the [Tutorial overview](/docs/getting-started/tutorial/tutorial-overview) for the full lesson list. --- # Lesson 5.2 - Add remote fields Source: https://hygraph.com/docs/getting-started/tutorial/tutorial-add-remote-fields In this lesson, you will add a remote field to the `Product` model. Remote fields fetch data from a remote source in the context of a specific model entry. The field uses a value from the entry itself, in this case `productSlug`, as the argument passed to the external API, so the reviews returned are always scoped to that product. This is different from a top-level remote field, which fetches remote data independently of any entry. That is covered in lesson 5.3. ## Add a remote field to the Product model You will use the `HyDemoAPI` remote source configured in lesson 5.1 to add a `Reviews` field to the **Product** model. 1. Open the **Product** model in the Schema editor and from the right sidebar, add a **REST** remote field. 2. Fill in the following information: | Field | Input | |---|---| | Display name | Reviews | | API ID | Auto-completed. Leave as is. | | Remote source | HyDemoAPI is selected by default. Leave as is. | | Method | GET is selected by default. Leave as is. | | Return type | Select `Reviews` from the dropdown | | Path | `/api/reviews/product/{{doc.productSlug}}` | The `Path` appends to the remote source base URL. `{{doc.productSlug}}` references the `productSlug` field on the current `Product` entry, which scopes the review data to that specific product. 3. Click **Add** to save. ## What's next Or, go to the [Tutorial overview](/docs/getting-started/tutorial/tutorial-overview) for the full lesson list. --- # Lesson 5.1 - Add a remote source Source: https://hygraph.com/docs/getting-started/tutorial/tutorial-add-remote-source In this lesson, you will add a remote source to the project schema. Before this step, the schema only returns data stored in Hygraph. After it, the schema includes queryable types from an external REST API that can be fetched alongside Hygraph content. A remote source is a custom resolver entry point in the schema. It accepts field values from other Hygraph fields as arguments, which is what makes it possible to fetch reviews scoped to a specific product in lesson 5.2. ## Configure the remote source You will connect a REST API using the Hygraph demo API. 1. Navigate to the **Schema editor** and click **+Add** next to **Remote Sources**. 2. Fill in the following information: | Field | Input | |---|---| | Select remote source type | Custom source | | Display Name | HyDemoAPI | | Type | REST | | Base URL | `https://demo-api.hygraph.workers.dev` | The base URL is the root endpoint for all remote fields that use this source. Individual remote fields append their own paths to this base. 3. Click **Add** to save the remote source configuration before adding type definitions. ## Add custom type definitions REST APIs return fixed data structures. To query a REST API as if it were native GraphQL, Hygraph needs to know the shape of the data it will receive. Custom type definitions provide that shape using GraphQL SDL. SDL stands for Schema Definition Language. It is the syntax used to define types and their fields in a GraphQL schema. The type definitions added here describe the structure of the data the `HyDemoAPI` returns, so Hygraph can expose those types in the project's GraphQL schema. 1. For each type definition, click **+Add custom type definition** and paste the snippets: ```GraphQL type Review { id: Int name: String product: String rating: Float comment: String } ``` ```GraphQL type Reviews { data: [Review] } ``` 2. Click **Add Remote Source** to save the configuration. Your finished remote source should look like this: The `Reviews` type wraps a list of `Review` entries. These two types are used by the remote fields in lessons 5.2 and 5.3. ## What's next Or, go to the [Tutorial overview](/docs/getting-started/tutorial/tutorial-overview) for the full lesson list. --- # Lesson 5.3 - Add top-level remote fields Source: https://hygraph.com/docs/getting-started/tutorial/tutorial-add-top-level-remote-fields In this lesson, you will add a top-level remote field to the **Query** system model and run a test query to confirm the remote source is working correctly. A top-level remote field fetches remote data outside the context of any model entry. Where the remote field added in lesson 5.2 only returns reviews for a specific product, a top-level remote field can return review data independently. This is useful for fetching all reviews, or for passing in an argument from the frontend rather than from a content entry. ## Add a top-level remote field You will add the field to the **Query** system model using the same `HyDemoAPI` remote source. ![Complete top-level remote field configuration](/images/docs/getting-started/top-level-remote-field.png) 1. Open the **Query** model in the Schema editor and from the right sidebar, add a **REST** remote field. 2. Fill in the following information: | Field | Input | |---|---| | Display name | Reviews | | API ID | Auto-completed. Leave as is. | | Remote source | HyDemoAPI is selected by default. Leave as is. | | Method | GET is selected by default. Leave as is. | | Return type | Select `Reviews` from the dropdown | | Input arguments | Click **+ Add input argument** to reveal the argument fields | | Input type | String | | API ID | productSlug | | Make field required | Select checkbox | | Path | `/api/reviews/product/{{args.productSlug}}` | The `Path` appends to the remote source base URL. The `{{args.productSlug}}` path argument is supplied by whoever runs the query: a frontend component, a developer testing in the API Playground, or any API client. This means the top-level remote field has no dependency on Hygraph content. It can be queried even if the `Product` model contains no entries at all. The remote field in [lesson 5.2](/docs/getting-started/tutorial/tutorial-add-remote-fields#add-a-remote-field-to-the-product-model) works differently. Its path uses `{{doc.productSlug}}`, which pulls the `productSlug` value from the specific `Product` entry being queried. The top-level remote field removes that dependency entirely. 3. Click **Add** to save. ## Test the top-level remote field Because the top-level remote field is not connected to a content entry, it can be tested immediately without creating any content first. 1. Navigate to the **API Playground**. 2. Run the following query: ```graphql query MyQuery { reviews(productSlug: "plaid-shirt") { data { comment id name product rating } } } ``` ```json { "data": { "reviews": { "data": [ { "comment": "After wearing this shirt for 2 days, I realized it was made of cotton. I am allergic to cotton.", "id": 3, "name": "Oregon Person", "product": "plaid-shirt", "rating": 1.5 }, { "comment": "This shirt is okay. It lost a button, but I sewed it back on.", "id": 6, "name": "Another Person", "product": "plaid-shirt", "rating": 3.5 }, { "comment": "I love this shirt. I wear it all the time.", "id": 7, "name": "Third Person", "product": "plaid-shirt", "rating": 4.5 } ] } } } ``` The response confirms the remote source is correctly configured. The Hygraph API returns external review data without any data migration and without a separate API call from the frontend. This is [Content Federation](/docs/core-concepts/content-federation) working as intended. The schema is now complete. Content creation starts in the next lesson. ## What's next Or, go to the [Tutorial overview](/docs/getting-started/tutorial/tutorial-overview) for the full lesson list. --- # Lesson 4.1 - Build components Source: https://hygraph.com/docs/getting-started/tutorial/tutorial-build-components In this lesson, you will build all components the project needs. These components must exist before you can attach them to models in [Lesson 4.2](/docs/getting-started/tutorial/tutorial-add-components-to-models). Some components depend on others. Build them in the order they appear in this lesson. `Clothing`, `Shoes`, `Accessories`, and `Decor` must exist before `Product Variant`. `Button` must exist before `Call to Action`. ## Related Products The `Related Products` component enables product pages to display other products a buyer might be interested in. It contains a read-only title field and a reference field that connects to other `Product` entries. 1. In the Schema editor, click **+Add** next to **Components**. 2. Provide the following information, and click **Add Component** to save: | Field | Input | |---|---| | Display Name | Related Products | | API ID | RelatedProduct | | Plural API ID | RelatedProducts | 3. Add the following fields to the `Related Products` component: **Single line text** - `Title`: | Field | Input | |---|---| | Display name | Title | | Use as title field | Select checkbox | | Make field required | Select checkbox | | Set initial value | Select checkbox, and enter `Related Products` in the initial value field | | Field visibility | Read only | The read-only title with an initial value ensures every instance of this component displays the same heading without editors being able to change it. **Reference** - `Products`: | Field | Input | |---|---| | Reference type | Allow only one model to be referenced | | Model to reference | Product | | Reference directions | One-way reference | | Allow multiple Products per RelatedProduct | Select checkbox | | Display name | Products | | API ID | products | | Description | Add 4 related products here | | Field visibility | Read / Write | 4. Click **Add** to save. Your `Related Products` component should look like this: ![Related Products component](/images/docs/getting-started/related-products-component.png) ## Product type components The store sells four product types: clothing, shoes, accessories, and decor. Each type requires different fields. Rather than creating four separate `Product` models, you will use four components, one per product type, and nest them inside a modular `Product Variant` component. The content editor then presents only the fields relevant to the selected product type. These four components use the enumerations created in [Lesson 3.1](/docs/getting-started/tutorial/tutorial-configure-enumerations). ### Clothing 1. Click **+Add** next to **Components**. 2. Provide the following information, and click **Add Component** to save: | Field | Input | |---|---| | Display Name | Clothing | | API ID | Clothing | | Plural API ID | ClothingItems | 3. Add the following fields: **Enumeration** - `Size`: | Field | Input | |---|---| | Display name | Size | | API ID | Auto-completed. Leave as is. | | Enumeration | Clothes size | | Make field required | Select checkbox | **Enumeration** - `Color`: | Field | Input | |---|---| | Display name | Color | | Enumeration | Wearable items color | | Make field required | Select checkbox | Your Clothing component should look like this: ![Clothing component](/images/docs/getting-started/clothing-component.png) ### Shoes 1. Click **+Add** next to **Components**. 2. Provide the following information, and click **Add Component** to save: | Field | Input | |---|---| | Display Name | Shoes | | API ID | Shoe | | Plural API ID | Shoes | 3. Add the following fields: **Enumeration** - `Size`: | Field | Input | |---|---| | Display name | Size | | Enumeration | Shoes size | | Make field required | Select checkbox | **Enumeration** - `Color`: | Field | Input | |---|---| | Display name | Color | | Enumeration | Wearable items color | | Make field required | Select checkbox | Your Shoes component should look like this: ![Shoes component](/images/docs/getting-started/shoes-component.png) ### Accessories 1. Click **+Add** next to **Components**. 2. Provide the following information, and click **Add Component** to save: | Field | Input | |---|---| | Display Name | Accessories | | API ID | Accessory | | Plural API ID | Accessories | 3. Add the following field: **Enumeration** - `Color`: | Field | Input | |---|---| | Display name | Color | | Enumeration | Wearable items color | | Make field required | Select checkbox | Your Accessories component should look like this: ![Accessories component](/images/docs/getting-started/accessories-component.png) ### Decor 1. Click **+Add** next to **Components**. 2. Provide the following information, and click **Add Component** to save: | Field | Input | |---|---| | Display Name | Decor | | API ID | Decor | | Plural API ID | DecorItems | 3. Add the following field: **Enumeration** - `Color`: | Field | Input | |---|---| | Display name | Color | | Enumeration | Decor items color | | Make field required | Select checkbox | Your Decor component should look like this: ![Decor component](/images/docs/getting-started/decor-component.png) ## Product Variant The `Product Variant` component contains a modular component field that presents `Clothing`, `Shoes`, `Accessories`, or `Decor` based on the product type an editor selects. This is the component that makes a single `Product` model work for all four product types. 1. Click **+Add** next to **Components**. 2. Provide the following information, and click **Add Component** to save: | Field | Input | |---|---| | Display Name | Product Variant | | API ID | ProductVariant | | Plural API ID | ProductVariants | 3. Add the following field: **Modular component** - `Product type`: | Field | Input | |---|---| | Display name | Product type | | API ID | productType | | Description | Select the product type to reveal fields | | Select allowed components | Clothing, Shoes, Accessories, Decor | 4. Click **Add** to save. Your Product Variant component should look like this: ![Product Variant component](/images/docs/getting-started/product-variant-component.png) ## Button The `Button` component holds a text label and a URL. It is used inside the `Call to Action` component in the next step. 1. Click **+Add** next to **Components**. 2. Provide the following information, and click **Add** to save: | Field | Input | |---|---| | Display Name | Button | | API ID | Button | | Plural API ID | Buttons | 3. Add the following fields: **Single line text** - `Text`: | Field | Input | |---|---| | Display name | Text | | Localize field | Select checkbox | **Slug** - `URL`: | Field | Input | |---|---| | Display name | URL | | Match a specific pattern | Select checkbox, then select `URL` from the dropdown | | Custom error message | Input value does not match the expected format. | Your `Button` component should look like this: ![Button component](/images/docs/getting-started/button-component.png) ## Call to Action The `Call to Action` component is used as a page section on Landing pages. It embeds the `Button` component created in the previous step. 1. Click **+Add** next to **Components**. 2. Provide the following information, and click **Add Component** to save: | Field | Input | |---|---| | Display Name | Call to Action | | API ID | CallToAction | | Plural API ID | CallToActions | 3. Add the following fields: **Single line text** - `Heading`: | Field | Input | |---|---| | Display name | Heading | | Localize field | Select checkbox | **Rich Text** - `Body`: | Field | Input | |---|---| | Display name | Body | | Localize field | Select checkbox | **Asset picker** - `Image`: | Field | Input | |---|---| | Display name | Image | **Basic component** - `Button`: | Field | Input | |---|---| | Display name | Button | | Select component | Button | Your `Call to Action` component should look like this: ![CTA component](/images/docs/getting-started/cta-component.png) ## Product Grid The `Product Grid` component is used as a page section on Landing pages. It displays a headline, a description, and a set of `Product` references. 1. Click **+Add** next to **Components**. 2. Provide the following information, and click **Add Component** to save: | Field | Input | |---|---| | Display Name | Product Grid | | API ID | ProductGrid | | Plural API ID | ProductGrids | 3. Add the following fields: **Single line text** - `Headline`: | Field | Input | |---|---| | Display name | Headline | | Localize field | Select checkbox | **Rich Text** - `Description`: | Field | Input | |---|---| | Display name | Description | **Reference** - `Products`: | Field | Input | |---|---| | Reference type | Allow only one model to be referenced | | Model to reference | Product | | Reference directions | One-way reference | | Allow multiple Products per ProductGrid | Select checkbox | | Display name | Products | | API ID | products | | Field visibility | Read / Write | Your `Product Grid` component should look like this: ![Product Grid component](/images/docs/getting-started/product-grid-component.png) ## Link component The `Link` component is included in the cloned project. It already contains two fields: `Display text` and `External URL`. You need to add a `Reference` field to it that connects it to the `Blog post`, `Landing page`, and `Product` models. This reference field could not be included in the original clone because it references models that did not exist until lesson 1.2. **This is the same layered schema pattern seen in lesson 1.2.** Models and components are built in layers because some connections can only be made after the things they connect exist. 1. In the Schema editor, under the **Components** section, select the **Link** component from the left sidebar. 2. From the right sidebar, add a **Reference** field with the following information: | Field | Input | |---|---| | Reference type | Allow multiple models to be referenced | | Model to reference | Blog post, Landing page, Product | | Reference directions | One-way reference | | Relation cardinality | To one | | Display name | Page | | API ID | page | | Field visibility | Read / Write | 3. Click **Add** to save. Your finished Link component should look like this: ![Link component](/images/docs/getting-started/link-component-finished.png) ## What's next Or, go to the [Tutorial overview](/docs/getting-started/tutorial/tutorial-overview) for the full lesson list. --- # Lesson 1.2 - Build your content models Source: https://hygraph.com/docs/getting-started/tutorial/tutorial-build-content-models In this lesson, you will create the base content models for the e-commerce project. These models are intentionally incomplete at this stage. References, components, and remote fields are added in later lessons. Every Hygraph project includes two system models: **Asset** and **Query**. These cannot be deleted, but you can add fields to them. The **Asset** model stores images. The **Query** model is where top-level remote fields are configured. You can create models to determine the structure of your content. You can create, rename, edit, and delete them. ## Asset model Before creating models, you need to add two fields to the **Asset** model. Product images require alt text and captions. Both fields are localized, so translated values can be added when Spanish localization is configured in lesson 6.2. 1. In your Hygraph project, click **Schema**, and then under **Models**, click **Asset**. 2. In the right sidebar, click **+Add** next to **Single line text**. Provide the following information, and click **Add** to save: | Field | Input | |---|---| | Display name | Alt text | | API ID | altText | | Description | Add alternative text for the image here | | Localize field | Select checkbox | 3. Add a second `Single line text` field for the caption, and click **Add** to save: | Field | Input | |---|---| | Display name | Caption | | API ID | caption | | Description | Add image caption here | | Localize field | Select checkbox | Your Asset model should look like this: ![Your Asset model](/images/docs/getting-started/asset-model.png) ## Product The `Product` model holds information for product listings. To create the `Product` model, follow these steps: 1. Click **+Add** next to **Models**. 2. Provide the following information, and click **Add Model** to save: | Field | Input | |---|---| | Display name | Product | | API ID | Product | | Plural API ID | Products | | Description | Tutorial project Product model | 3. Add the following fields to the `Product` model: **Single line text** - `Product name`: | Field | Input | |---|---| | Display name | Product name | | API ID | Auto-completed. Leave as is. | | Use as title field | Select checkbox | | Localize field | Select checkbox | | Make field required | Select checkbox | | Set field as unique | Select checkbox | Each API ID is a unique identifier across the project. The system will not allow duplicates. Display names do not have this restriction, but keeping them descriptive helps collaborators navigate the schema. **Slug** - `Product Slug`: | Field | Input | |---|---| | Display name | Product Slug | | API ID | Auto-completed. Leave as is. | | Lowercase | Leave selected | | Make field required | Select checkbox | | Set field as unique | Select checkbox | | Match a specific pattern | Select checkbox, then select `Slug` from the dropdown | | Custom error message | Input value does not match the expected format. | **Asset picker** - `Product image`: | Field | Input | |---|---| | Display name | Product image | | Allow multiple assets | Select checkbox | **Rich Text** - `Product description`: | Field | Input | |---|---| | Display name | Product description | | API ID | Auto-completed. Leave as is. | | Enable embedding | Select checkbox. Select the **Asset** and **Product** models from the dropdown. | | Localize field | Select checkbox | **Enabling embedding on the Product description field** allows editors to embed images and product entries directly in the description body. The Product category model does not exist yet, so you will add it after creating that model. **Float** - `Product price`: | Field | Input | |---|---| | Display name | Product price | | API ID | Auto-completed. Leave as is. | Your Product model should look like this: ![Your Product model so far](/images/docs/getting-started/product-model-initial-fields.png) This model is not finished. A product variant component is added in lesson 4 and a reviews remote field in lesson 5. ## Product category The `Product category` model holds categories that products are assigned to. It connects to the `Product` model through a reference, which we configure in lesson 2. To create the `Product category` model, follow these steps: 1. Click **+Add** next to **Models**. 2. Provide the following information, and click **Add Model** to save: | Field | Input | |---|---| | Display Name | Product category | | API ID | ProductCategory | | Plural API ID | ProductCategories | | Description | Select a product category | 3. Add the following fields to the `Product category` model: **Single line text** - `Category name`: | Field | Input | |---|---| | Display name | Category name | | Use as title field | Select checkbox | | Localize field | Select checkbox | | Make field required | Select checkbox | | Set field as unique | Select checkbox | **Slug** - `Slug`: | Field | Input | |---|---| | Display name | Slug | | Lowercase | Leave selected | | Make field required | Select checkbox | | Set field as unique | Select checkbox | | Match a specific pattern | Select checkbox | | Custom error message | Input value does not match the expected format. | **Rich Text** - `Description`: | Field | Input | |---|---| | Display name | Description | | Localize field | Select checkbox | Your `Product category` model should look like this: ![Your Product category model so far](/images/docs/getting-started/product-category-model-fields.png) Now return to the `Product` model and edit the `Product description` Rich Text field. Add **Product category** to the **Enable embedding** dropdown. ![Product category in Rich Text embeds](/images/docs/getting-started/product-category-rich-text.png) **Schema building is not always linear.** Models are interconnected, and you will sometimes need to return to an earlier model after creating a later one. This is expected. ## Blog post The Blog post model holds articles that promote products. To create the `Blog Post` model, follow these steps: 1. Click **+Add** next to **Models**. 2. Provide the following information, and click **Add Model** to save: | Field | Input | |---|---| | Display Name | Blog Post | | API ID | BlogPost | | Plural API ID | BlogPosts | | Description | Articles to promote our products | 3. Add the following fields to the `Blog Post` model: **Single line text** - `Title`: | Field | Input | |---|---| | Display name | Title | | Use as title field | Select checkbox | | Localize field | Select checkbox | | Make field required | Select checkbox | | Set field as unique | Select checkbox | **Slug** - `Slug`: | Field | Input | |---|---| | Display name | Slug | | Generate slug from template | Select checkbox | | Slug template | `{title}` | | Lowercase | Select checkbox | | Match a specific pattern | Select checkbox, then select `Slug` from the dropdown | | Custom error message | Input value does not match the expected format. | **Rich Text** - `Body`: | Field | Input | |---|---| | Display name | Body | | Localize field | Select checkbox | | Enable embedding | Select checkbox. Select **Blog Post**, **Product**, and **Product category** from the dropdown. | Your Blog post model should look like this: ![Blog post model fields](/images/docs/getting-started/blog-post-model-fields.png) ## Seller information The Seller information model holds business details referenced from the landing page. To create the `Seller information` model, follow these steps: 1. Click **+Add** next to **Models**. 2. Provide the following information, and click **Add Model** to save: | Field | Input | |---|---| | Display Name | Seller information | | API ID | SellerInformation | | Plural API ID | SellerInformations | 3. Add the following fields to the `Seller information` model: **Single line text** - `Business name`: | Field | Input | |---|---| | Display name | Business name | | Use as title field | Select checkbox | | Localize field | Select checkbox | | Make field required | Select checkbox | **Slug** - `Slug`: | Field | Input | |---|---| | Display name | Slug | | Lowercase | Leave selected | | Make field required | Select checkbox | | Set field as unique | Select checkbox | | Match a specific pattern | Select checkbox, then select `Slug` | | Custom error message | Input value does not match the expected format. | **Asset picker** - `Business logo`: | Field | Input | |---|---| | Display name | Business logo | **Rich Text** - `Business description`: | Field | Input | |---|---| | Display name | Business description | | API ID | Auto-completed. Leave as is. | | Localize field | Select checkbox | ## Landing page The Landing page model supports flexible page structures. It is intentionally minimal at this stage. Component fields and references are added in lessons 2 and 4. 1. Click **+Add** next to **Models**. 2. Provide the following information, and click **Add Model** to save: | Field | Input | |---|---| | Display Name | Landing page | | API ID | LandingPage | | Plural API ID | LandingPages | 3. Add the following fields to the `Landing page` model: **Single line text** - `Landing page title`: | Field | Input | |---|---| | Display name | Landing page title | | Use as title field | Select checkbox | | Localize field | Select checkbox | **Slug** - `Link`: | Field | Input | |---|---| | Display name | Link | | API ID | Auto-completed. Leave as is. | | Generate slug from template | Select checkbox | | Slug template | `{landingPageTitle}` | | Lowercase | Leave selected | | Set field as unique | Select checkbox | | Match a specific pattern | Select checkbox. Select **Custom** and enter: `^[a-z0-9\/]+(?:[-\/][a-z0-9]+)*$` | | Custom error message | Input value does not match the expected format. | ## Navigation The Navigation model is included in the cloned project. At this stage it contains the `Nav ID` field, which is a slug field used as the navigation identifier. This model is incomplete. A way to hold navigation links is added in lesson 4.2 after creating the Link component. ## What's next Or, go to the [Tutorial overview](/docs/getting-started/tutorial/tutorial-overview) for the full lesson list. --- # Lesson 4 - Components Source: https://hygraph.com/docs/getting-started/tutorial/tutorial-components-overview This section covers building the components the e-commerce project needs and attaching them to the correct models. A component is a reusable set of fields defined once in the schema and embedded in models as a basic or modular component field. The components built here solve two specific problems in this project. The `Product Variant` modular component makes it possible for a single `Product` model to handle clothing, shoes, accessories, and decor, each with its own required fields, without creating four separate models. The `Stripes` modular component on the `Landing page` model gives editors the ability to compose pages from a variable combination of CTA and Product Grid sections. By the end of lesson 4.2, the following models are updated: - `Product` — `Product Variant` and `Related Products` components attached - `Landing page` — `Stripes` modular component attached - `Navigation` — `Nav Link` component attached, completing the model left open in lesson 1.2 ## Components and component fields A component and a component field are two different things. You create the component first, then embed it in a model using a component field. Hygraph supports two types of component fields: | Type | What it does | |---|---| | Basic component field | Embeds a single component type. Use this when the field always contains the same structure. | | Modular component field | Allows editors to choose from a set of component types. Use this when editors need to select which component fills the field. | The product type components (`Clothing`, `Shoes`, `Accessories`, `Decor`) use a modular component field inside `Product Variant` because the content editor needs to present a choice. The `Related Products` component uses a basic component field because no choice is required. It always contains the same structure. ## Build order matters Some components depend on others. Build them in this order: 1. Related Products 2. Clothing, Shoes, Accessories, Decor (in any order) 3. Product Variant (depends on 2) 4. Button 5. Call to Action (depends on 4) 6. Product Grid 7. Link component modification (pre-built in the cloned project) For the complete components documentation, see [Components](/docs/developer-guides/schema/components). ## Lessons in this section | Lesson | What you will do | |---|---| | [4.1 Build components](/docs/getting-started/tutorial/tutorial-build-components) | Create components and configure their fields, references, and nested component fields | | [4.2 Add components to your models](/docs/getting-started/tutorial/tutorial-add-components-to-models) | Attach the components to the `Product`, `Landing page`, and `Navigation` models to complete the schema | ## What's next Or, go to the [Tutorial overview](/docs/getting-started/tutorial/tutorial-overview) for the full lesson list. --- # Lesson 3.1 - Configure enumerations Source: https://hygraph.com/docs/getting-started/tutorial/tutorial-configure-enumerations In this lesson, you will create enumerations for product size and color values that the product variant components will use. These must exist in the schema before you can build the product variant components in lesson 4. ## Add enumerations All enumerations follow the same process. In the Schema editor, click **+Add** next to **Enumerations** in the left sidebar. Fill in the display name and API ID, then add enumeration values one by one by entering the display name and clicking **+ Add**. Click **Add Enumeration** to save. 1. Go to **Schema**, and click **+Add** next to **Enumerations**. 2. Provide the following values for each enumeration, and click **Add Enumeration** to save. **Clothes size** |Field |Input | |-------------------|-------------------------------------------------| |Display name |Clothes size | |API ID |ClothesSize | |Enumeration values |One by one, add the following: XS, S, M, L, XL | **Shoes size** | Field | Input | |---|---| | Display name | Shoes size | | API ID | ShoesSize | | Enumeration values | Size_35, Size_36, Size_37, Size_38, Size_39, Size_40, Size_41, Size_42, Size_43, Size_44 | **Wearable items color** | Field | Input | |---|---| | Display name | Wearable items color | | API ID | WearableItemsColor | | Enumeration values | Red, Green, Blue, Black, White | **Decor items color** | Field | Input | |---|---| | Display name | Decor items color | | API ID | DecorColor | | Enumeration values | Gold, Silver, Bronze, Cobalt | After adding all enumerations, the left sidebar should look like this: ![Your enumerations listed in the schema](/images/docs/getting-started/enumerations-listed.png) **Why separate enumerations for shoes and clothes sizes?** Shoes and clothing use different size scales. A single "size" enumeration would mix EU shoe sizes with clothing sizes, producing a dropdown that is inconvenient to use in the content editor. Separate enumerations keep each product type's fields meaningful and required. ## What's next Or, go to the [Tutorial overview](/docs/getting-started/tutorial/tutorial-overview) for the full lesson list. --- # Lesson 2.1 - Configure reference fields Source: https://hygraph.com/docs/getting-started/tutorial/tutorial-configure-reference-fields In this lesson, you will add reference fields to the `Product` and `Landing page` models to connect them to other models in the schema. The reference type, direction, and cardinality configured here determine exactly what you will query later in the API Playground. ## Products and categories Products need to be assignable to multiple categories, and categories need to display all their products. This requires a many-to-many, two-way reference. A two-way reference is required so you can query in both directions: from a product to its categories, and from a category to all its products. 1. Open the **Product** model in the Schema editor and from the right sidebar, add a **Reference** field. 2. Configure the reference field with the following information: **Define relationship** | Field | Input | |---|---| | Reference type | Allow only one model to be referenced | | Model to reference | ProductCategory | | Reference directions | Two-way reference | | Allow multiple Products per ProductCategory | Select checkbox | | Allow multiple ProductCategories per Product | Select checkbox | | Relationship cardinality | The graphic displays that the relationship is of type many-to-many | **Configure reference** | Field | Input | |---|---| | Display name | Pre-configured. Leave as is. | | API ID | Pre-configured. Leave as is. | | Description | Select the categories that apply to your product | | Field visibility | Read / Write | **Configure reverse field** | Field | Input | |---|---| | Display name | Pre-configured. Leave as is. | | API ID | Pre-configured. Leave as is. | | Field visibility | Read / Write | 3. Click **Add** to save. The reference field appears at the bottom of the model. Use the six-dot handle to drag it above the slug field. **What this enables:** In lesson 7, you will be able to query `productCategory(where: { slug: "sportswear" }) { products { productName } }`. The query returns all products within that category. This is possible because of the two-way direction configured here. ## Landing page references The `Landing page` model needs to display featured blog posts, product categories, and seller information. Each of these is a one-way reference. The `Landing page` queries into those models, but those models do not need to query back into `Landing pages`. 1. Open the **Landing page** model in the Schema editor and from the right sidebar under **Relation**, select **Reference**. 2. Configure the reference field for the `Blog post` model with the following information: **Define relationship** | Field | Input | |---|---| | Reference type | Allow only one model to be referenced | | Model to reference | BlogPost | | Reference directions | One-way reference | | Allow multiple BlogPosts per LandingPage | Select checkbox | **Configure reference** | Field | Input | |---|---| | Display name | Pre-configured. Leave as is. | | API ID | Pre-configured. Leave as is. | | Description | Featured item | | Field visibility | Read / Write | 3. Click **Add** to save. 4. Add another **Reference** field from the right sidebar. Configure the reference field for the `Product category` model with the following information: **Define relationship** | Field | Input | |---|---| | Reference type | Allow only one model to be referenced | | Model to reference | ProductCategory | | Reference directions | One-way reference | | Allow multiple ProductCategories per LandingPage | Select checkbox | **Configure reference** | Field | Input | |---|---| | Display name | Pre-configured. Leave as is. | | API ID | Pre-configured. Leave as is. | | Description | Browse our categories | | Field visibility | Read / Write | 5. Click **Add** to save. 6. Add another **Reference** field from the right sidebar. Configure the reference field for the `Seller information` model with the following information: **Define relationship** | Field | Input | |---|---| | Reference type | Allow only one model to be referenced | | Model to reference | SellerInformation | | Reference directions | One-way reference | | Relation cardinality | To one | **Configure reference** | Field | Input | |---|---| | Display name | Pre-configured. Leave as is. | | API ID | Pre-configured. Leave as is. | | Description | Business information | | Field visibility | Read / Write | 7. Click **Add** to save. Your Landing page model should now look like this: ![Your Landing page model so far](/images/docs/getting-started/landing-page-model-with-references.png) **Why one-way for Landing page references, but two-way for Products and categories?** A Blog post, Product category, or Seller information entry has no use case for querying which landing pages reference it. The two-way reference on Products and categories exists because the project needs to navigate from both sides. Where that bidirectional query isn't needed, a one-way reference keeps the schema clean. ## What's next Or, go to the [Tutorial overview](/docs/getting-started/tutorial/tutorial-overview) for the full lesson list. --- # Lesson 9.1 - Configure webhooks Source: https://hygraph.com/docs/getting-started/tutorial/tutorial-configure-webhooks In this lesson, you will configure a webhook that triggers a new deployment whenever a product entry is published in Hygraph. Without this, publishing content in Hygraph does not update the live storefront and the deployed site continues to show the version built at the last deployment. ## Generate a build hook Before configuring the webhook in Hygraph, generate a build hook URL on your deployment platform. Hygraph will send a POST request to this URL whenever the webhook fires. ### Netlify 1. Go to your site in Netlify and navigate to **Project configuration > Build & deploy > Continuous deployment > Build hooks**. 2. In the **Build hook name** field, enter `Deploy from Hygraph`. 3. In the **Branch to build** dropdown, select `main`. 4. Click **Save**. 5. Copy the generated URL. ### Vercel 1. Go to your project in Vercel and navigate to **Settings > Git > Deploy Hooks**. 2. In the hook name field, enter `Deploy from Hygraph`. 3. In the branch field, enter `main`. 4. Click **Create Hook**. 5. Copy the generated URL. ## Configure the webhook in Hygraph 1. Navigate to **Project Settings > Automation > Webhooks** in your Hygraph project. 2. Click **Add webhook**. 3. Fill in the following information: | Field | Input | |---|---| | Name | Deploy to Storefront | | Description | Trigger published | | Include payload | Enable to send data via the webhook | | Method | POST | | URL | Paste the build hook URL copied from Netlify or Vercel | | Secret key | Generate a strong random string and enter it here. See the note below. | | Headers | Leave blank | **The secret key** is how the receiving endpoint verifies the request came from Hygraph. Without it, any POST request to the build hook URL triggers a deployment. Add a strong secret and store it securely. 4. Under **Triggers**, configure the following: | Field | Input | |---|---| | Content model | Product | | Stage | PUBLISHED | | Actions | Leave empty | | Sources | Leave empty | You need to select the `Product` model because product content is what the storefront displays. Selecting `PUBLISHED` means the webhook only fires when content is published, and saving a content entry does not trigger a deployment. Leaving `Actions` and `Sources` empty acts as a wildcard: all publish actions from all sources trigger the webhook. 5. Click **Add webhook** to save. Publishing any `Product` entry in Hygraph automatically triggers a new storefront deployment. ## Verify the webhook To verify the webhook is working, publish a content entry and confirm a new build starts on your deployment platform. 1. Navigate to **Content** and open any `Product` entry. 2. Modify the product entry, for example, edit the product description. 3. Click **Save & Publish**. 4. Check your deployment platform for a new build. - **Netlify**: Navigate to **Deploys** in your Netlify site and check if a new build appears at the top of the list, triggered by the webhook. - **Vercel**: Navigate to **Deployments** in your Vercel project and check if a new deployment appears, triggered by the webhook. If no build starts, check the following: - The webhook URL matches the build hook URL exactly. There should be no trailing spaces or missing characters. - The content entry you published belongs to the **Product** model. The trigger is scoped to Product only. - The entry moved to the `PUBLISHED` stage. Saving an entry does not trigger the webhook. ## What's next Or, go to the [Tutorial overview](/docs/getting-started/tutorial/tutorial-overview) for the full lesson list. --- # Lesson 8.2 - Connect the storefront Source: https://hygraph.com/docs/getting-started/tutorial/tutorial-connect-storefront In this lesson, you will configure Content API access in Hygraph and add two environment variables to the starter. By the end, the storefront is running locally and displaying published content. ## Two access modes Before configuring anything, it helps to understand why there are two separate configurations. Hygraph exposes a single Content API endpoint. The starter uses two different ways to access content through that endpoint: - **The Content API** serves the storefront for public visitors. It only returns content in the `PUBLISHED` stage. No authentication is required. - **A Permanent Auth Token (PAT)** grants the frontend access to `DRAFT` content. It is used only for the preview URL configured in lesson 8.3. ## Configure Content API permissions The Public Content API needs read permissions initialized before unauthenticated requests can return content. 1. Navigate to **Project settings > Access > Content API**. 2. In the **Content Permissions** box, click **Initialize defaults**. The content permissions box now looks like this: ![Content permissions initialized](/images/docs/getting-started/public-content-api-permissions.png) 3. Navigate to **Project settings > Access > Endpoints**. Copy the **High Performance Content API** endpoint URL from this screen. You will need it while [adding environment variables](#add-environment-variables). For a full reference on public API permissions, see [Public API permissions](/docs/api-reference/basics/authorization#public-api-permissions). ## Create a PAT for draft content 1. Navigate to **Project settings > Access > Permanent Auth Tokens**. 2. Click **+ Add token**. 3. Enter a name and optional description, select **DRAFT** as the default stage for content delivery, and click **Add & configure permissions**. 4. On the token details screen, copy the token value. You will need it while [adding environment variables](#add-environment-variables). For a full reference on Permanent Auth Tokens, see [Authorization](/docs/api-reference/basics/authorization). ## Add environment variables Copy the `.env.sample` file and rename it to `.env.local`: ```bash cp .env.sample .env.local ``` Open `.env.local` and add the two values collected above: ```bash HYGRAPH_ENDPOINT=YOUR_CONTENT_API_ENDPOINT_HERE HYGRAPH_DEV_AUTH_TOKEN=YOUR_DRAFT_TOKEN_HERE HYGRAPH_PREVIEW_SECRET= ``` Leave `HYGRAPH_PREVIEW_SECRET` empty for now. You will configure it in the next lesson. ## Run the storefront Start the local development server: ```bash npm run dev ``` Open `http://localhost:3000` in your browser. The storefront should display your published content. ![Starter homepage](/images/docs/getting-started/starter-homepage.png) ![Product page](/images/docs/getting-started/product-page.png) The Content API only returns content in the `PUBLISHED` stage. If the storefront is blank, open the content editor and verify that all content entries created in lesson 6.1 are published. The storefront renders your landing pages, product pages, navigation, and review data using the same GraphQL queries practiced in lesson 7.1. The `utils/` directory in the starter contains the query functions. Spend some time exploring the code in your editor to understand how the Hygraph content maps to the frontend components. ## What's next Or, go to the [Tutorial overview](/docs/getting-started/tutorial/tutorial-overview) for the full lesson list. --- # Lesson 1 - Content models Source: https://hygraph.com/docs/getting-started/tutorial/tutorial-content-models-overview This section covers planning the content structure of the e-commerce project and building it in the Hygraph schema editor. The field names, model relationships, and component structures defined here are what the API exposes and the frontend consumes. Everything configured in later lessons, such as queries, mutations, frontend connection, and webhooks, depends on the schema built here. By the end of lesson 1.2, the project contains the following models with their initial fields: - Product - Product category - Blog post - Seller information - Landing page - Navigation These models are intentionally incomplete at this stage. References are added in lesson 2, components in lesson 4, and remote fields in lesson 5. The schema is built in layers, not all at once. ## Key terms Before building, the following terms appear in the schema editor: ![Project models](/images/docs/getting-started/hygraph-project-models.png) **Schema:** The complete content structure of your project. It contains your models, components, enumerations, and remote sources. **Models:** Containers that define the shape of a content type. A `Product` model defines what a product entry looks like. When an editor creates a product, they fill in the fields that model defines. **Fields:** The individual data points stored on a model. A `Product` model might contain a `name` field (Single line text), a `price` field (Float), and an `image` field (Asset picker). Fields have types, and those types determine what data can be stored and how it can be queried. In GraphQL, field types are called scalars. Hygraph provides scalar types for strings, integers, floats, booleans, dates, colors, geo-coordinates, and more. These appear in the schema editor's right sidebar when adding a field. ## Lessons in this section The following lessons apply content modeling concepts directly to this project. For a deeper understanding of those concepts, read [Content modeling in Hygraph](/docs/core-concepts/content-modeling/content-modeling-in-hygraph). | Lesson | What you will do | |---|---| | [1.1 Design your content models](/docs/getting-started/tutorial/tutorial-design-content-models) | Map the models, components, enumerations, and remote data the project needs before touching the schema editor | | [1.2 Build your content models](/docs/getting-started/tutorial/tutorial-build-content-models) | Create the base models and add their initial fields in the Hygraph schema editor | ## What's next Or, go to the [Tutorial overview](/docs/getting-started/tutorial/tutorial-overview) for the full lesson list. --- # Content Source: https://hygraph.com/docs/getting-started/tutorial/tutorial-content-overview This section covers creating all the content the e-commerce project needs and understanding how the content editor reflects the schema built in earlier lessons. Every field, dropdown, reference, and component you see here was configured in lessons 1 through 5. If a product variant presents only size and color fields for the selected product type, that is the modular component from lesson 4.1 working as intended. If a category reference allows selecting multiple categories per product, that is the many-to-many configuration from lesson 2.1. The content editor is where schema decisions become testable. By the end of lesson 6.3, the project contains: - Published product categories - Core product entries with related products, images, and variants - Seller information entry - Landing pages with `Stripes` components - Navigation entry with links - Localized Spanish content for the Summer Campaign and products - Published content ready to query in the API Playground ## Lessons in this section | Lesson | What you will do | |---|---| | [6.1 Create content](/docs/getting-started/tutorial/tutorial-create-content) | Create product categories, seller information, products, landing pages, and navigation entries | | [6.2 Localize content](/docs/getting-started/tutorial/tutorial-localize-content) | Add a Spanish locale and write localized content for the Summer Campaign and products | | [6.3 Understand content stages](/docs/getting-started/tutorial/tutorial-content-stages) | Learn how DRAFT and PUBLISHED stages affect what the Content API returns and what the frontend displays | ## What's next Or, go to the [Tutorial overview](/docs/getting-started/tutorial/tutorial-overview) for the full lesson list. --- # 6.3 Understand content stages Source: https://hygraph.com/docs/getting-started/tutorial/tutorial-content-stages Content stages in Hygraph are not just an editorial workflow feature. They determine what the Content API returns and what your frontend displays. Understanding this now prevents the most common source of confusion when connecting the frontend in lesson 8. ## DRAFT and PUBLISHED Hygraph provides two system content stages: `DRAFT` and `PUBLISHED`. Every content entry starts in `DRAFT`. When you click **Save & Publish**, the entry is promoted to `PUBLISHED`. At that point, the entry exists in both stages simultaneously. `DRAFT` always reflects the current state of the entry, and `PUBLISHED` reflects the last published version. When you edit a published entry and click **Save** without publishing, the entry becomes **outdated**. The `DRAFT` stage contains changes that have not yet been promoted to `PUBLISHED`. The published version on the frontend remains unchanged until you publish again. ![Table showing content in stages DRAFT, PUBLISHED, and outdated](/images/docs/getting-started/content-stages-comparison.png) **The API consequence:** Hygraph uses a single Content API endpoint. Which stage you read depends on how access is configured via public permissions, a Permanent Auth Token (PAT), or stage headers and query parameters. Unauthenticated public access with default permissions only returns entries that have been published. If an entry has never been published, those requests return no data for that entry. In lesson 8.2, you will configure a Permanent Auth Token (PAT) that grants the frontend access to draft content for preview purposes. The public storefront uses unauthenticated access configured for the `PUBLISHED` stage. Understanding the distinction now means that when you see different results in the API Playground versus the frontend, you will know why. Hygraph also supports custom content stages. For example, a Review stage between DRAFT and PUBLISHED for editorial workflows that require approval before publishing. See [Content stages](/docs/developer-guides/content/content-stages) for details. ## Save vs Save & Publish Every time you create or edit a content entry, you choose between two actions: - **Save** stores the changes in the `DRAFT` stage only. The published version is not updated. Use this when content needs review before going live. - **Save & Publish** stores the changes in `DRAFT` and promotes them to `PUBLISHED` simultaneously. Use this when content is ready to be consumed by the frontend. ![Save and Save & publish buttons](/images/docs/getting-started/save-publish-options.png) All entries created in lesson 6.1 used **Save & Publish**, which means they exist in both `DRAFT` and `PUBLISHED`. If you used **Save** only at any point, those entries will not appear in responses configured for the `PUBLISHED` stage, including in the connected storefront. If your frontend appears to be missing content after connecting it in lesson 8, check whether the relevant entries have been published. An entry that exists in `DRAFT` only is not visible when querying for the `PUBLISHED` stage. ## Content duplication Hygraph lets you duplicate content entries to save time when creating similar entries. To duplicate an entry, open it in the edit view and click the duplication icon at the top of the screen. The duplicate loads with the same field values as the original, with `(copy)` appended to the title field. If a duplicated entry contains reference fields, some relations are duplicated and others are not. The system notifies you when this is the case. See [Duplicating content](/docs/developer-guides/content/duplicating-content) for details on which relations are preserved. ## What's next Or, go to the [Tutorial overview](/docs/getting-started/tutorial/tutorial-overview) for the full lesson list. --- # 6.1 Create content Source: https://hygraph.com/docs/getting-started/tutorial/tutorial-create-content In this lesson, you will populate the e-commerce project with content. The fields, dropdowns, references, and components in the content editor are a direct result of the schema built in lessons 1 through 5. If anything looks unexpected, it is worth returning to the relevant schema lesson to verify the configuration. The lesson is structured in stages because some content depends on other content existing first. Products cannot have related products until other products exist. Landing pages cannot reference blog posts or categories until those entries are published. Follow the sections in order. ## Assets The cloned project already contains product images. You need to add alt text and a caption to the headband image to get started. 1. In your Hygraph project, click **Assets**. 2. Type `headband` in the search filter. 3. Open the first result to edit the asset. 4. Fill in the following fields and click **Save & Publish**: | Field | Input | |---|---| | Alt text | Cotton headband | | Caption | A soft white cotton headband | 5. Click the back arrow next to **Edit Asset** at the top left to return to the assets table. 6. Click the **X** next to the search filter to reset it. For alt text and caption data for all remaining assets, see [Create content — additional practice](/docs/getting-started/tutorial/tutorial-create-content-additional-practice#assets). Adding this data is optional and does not affect the frontend connection. ## Product categories The `Product category` model was created in lesson 1.2 and connected to the `Product` model through a many-to-many reference in lesson 2.1. Each category entry created here can later be assigned to multiple products, and each product can belong to multiple categories. No product entries exist yet, so you will create the categories first and add product assignments later. 1. Go to **Content**, and under **Default Views**, click **Product category**. 2. Click **+ Add entry**. 3. Create the following entry and click **Save & Publish**: | Field | Input | |---|---| | Category name | Clothes | | Slug | clothes | | Description | You will find clothes here | Repeat the process for each of the following categories: | Category name | Slug | Description | |---|---|---| | Shoes | shoes | You will find shoes here | | Sportswear | sportswear | You will find sportswear here | | Urban | urban | You will find urban-style items here | | New arrival | new-arrival | You will find our latest arrivals here | | Decor | decor | You will find decor items here | | Accessories | accessories | You will find accessories here | Some categories overlap by design. Because the reference between products and categories is many-to-many, one product can belong to both New arrival and Sportswear at the same time. Your Product categories view should now look like this: ![Product categories in the content editor with entries](/images/docs/getting-started/product-category-table-full.png) ## Seller information The Seller information model holds business details that the landing page references. Create a single entry. 1. Go to **Content**, and under **Default Views**, click **Seller information**. 2. Click **+ Add entry**. 3. Fill in the following fields and click **Save & Publish**: | Field | Input | |---|---| | Business name | My business | | Slug | my-business | | Business logo | Click **Add business logo**, select the Hygraph logo from your assets, and click the relation icon in the first column to add it | | Business description | This is a description of my business | Your Seller information view should look like this: ![Seller information content editor view with entry](/images/docs/getting-started/seller-information-table.png) ## Products Product entries are created in two steps. In this step, you will create the basic product listing. Related products are added in the next step, once all product entries exist. 1. Go to **Content**, and under **Default Views**, click **Product**. 2. Click **+ Add entry**. ![Add product content entry](/images/docs/getting-started/add-product-content-entry.png) 3. Create the first entry using the following information: | Field | Input | |---|---| | Product name | Black leather shoes | | Product slug | black-leather-shoes | | Product category | Click **Add existing Product categories** and select **Shoes** and **New arrival** | | Product image | Click **Add Product images**, type `black leather shoes` in the search filter, and click **Add selected images** | | Product description | These black leather shoes seamlessly blend sophistication with durability, making them a versatile choice for both formal occasions and everyday wear. The sleek design and high-quality leather ensure a timeless appeal that complements any outfit. | | Product variant | Click **Add component**, select **Shoes**, then set Size to `Size_40` and Color to `Black` | | Product price | 170 | `Product variant` is the modular component field built in lesson 4.1. Clicking **Add component** presents the four product type options — `Clothing`, `Shoes`, `Accessories`, `Decor`. Selecting `Shoes` reveals only the shoe size and color fields, not the clothing size fields. This is the modular component working as configured. 4. Click **Save & Publish**. ![Product content entry](/images/docs/getting-started/product-content-entry.png) Now create the remaining product entries using the following information: **Blue running shoes** | Field | Input | |---|---| | Product name | Blue running shoes | | Product slug | blue-running-shoes | | Product category | Select **Sportswear** and **Shoes** | | Product image | Select images with file name **blue running shoes** | | Product description | These blue running shoes offer both form and function, providing exceptional support and style for your active pursuits. The breathable material and cushioned sole ensure a comfortable experience mile after mile. | | Product variant | Select **Shoes**, then Size `Size_42` and Color `Blue` | | Product price | 130 | **Plaid shirt** | Field | Input | |---|---| | Product name | Plaid shirt | | Product slug | plaid-shirt | | Product category | Select **New arrival**, **Urban**, and **Clothes** | | Product image | Select images with file name **blue plaid shirt** | | Product description | This plaid shirt exudes a timeless charm, perfect for a casual day out or a relaxed evening gathering. The classic checkered pattern and comfortable fit make it a wardrobe staple for effortless style. | | Product variant | Select **Clothing**, then Size `XL` and Color `Blue` | | Product price | 60 | **Necklace** | Field | Input | |---|---| | Product name | Necklace | | Product slug | necklace | | Product category | Select **Accessories** | | Product image | Select images with file name **necklace** | | Product description | This white necklace is crafted from parts of seashells, evoking a relaxed, beachy vibe. Perfect for those looking to add a touch of the sea to their daily style. | | Product variant | Select **Accessories**, then Color `White` | | Product price | 12 | **Headband** | Field | Input | |---|---| | Product name | Headband | | Product slug | headband | | Product category | Select **Accessories** | | Product image | Select images with file name **headband** | | Product description | This white cotton headband provides an elegant and sophisticated touch to your hairstyle. Perfect for completing your look with a blend of fashion and comfort. | | Product variant | Select **Accessories**, then Color `White` | | Product price | 9 | For adding more product entries, see [Create content — additional practice](/docs/getting-started/tutorial/tutorial-create-content-additional-practice#products). Adding these is optional but some API Playground query exercises in lesson 7.1 reference them. ## Related products Now that you've created all product entries, you can add related products to each one. 1. Open the **Headband** entry to edit it. 2. Scroll to the **Related Products** component and click **+ Add Related Products**. The **Title** field is read-only and displays `Related Products`. This is because you set the field to read-only with an initial value when building the component in lesson 4.1. So, every instance of this component shows the same heading. 3. Click **Add existing products** and select: black leather shoes, necklace, blue running shoes, and plaid shirt. 4. Click **Add selected products**, then click **Save & Publish**. ![Component with related products](/images/docs/getting-started/related-products-component.png) Repeat for the remaining product entries: | Product entry | Related products | |---|---| | Blue running shoes | black leather shoes, necklace, headband, plaid shirt | | Black leather shoes | blue running shoes, necklace, headband, plaid shirt | | Necklace | black leather shoes, blue running shoes, headband, plaid shirt | | Plaid shirt | black leather shoes, necklace, headband, blue running shoes | For related product assignments for the additional practice entries, see [Create content — additional practice](/docs/getting-started/tutorial/tutorial-create-content-additional-practice#related-products). ## Blog post Create a blog post entry to use on the landing page. 1. Go to **Content**, and under **Default Views**, click **Blog post**. 2. Click **+ Add entry** and fill in the following: | Field | Input | |---|---| | Title | Our new leather shoes | | Slug | Auto-completes from the title. Leave as is. | | Body | Type `Take a look at this awesome new arrival`. Then click **Embed**, select **Block**, choose the **Product** model from the dropdown, click **Select model**, and select the **Black leather shoes** entry. | 3. Click **Save & Publish**. Your blog post entry should look like this: ![Blog post complete entry](/images/docs/getting-started/blog-post-entry.png) ## Landing pages You will create two landing pages: a homepage and a Summer Campaign page. The homepage could not have been created earlier because it references seller information, blog posts, and products that needed to exist first. ### Homepage 1. Go to **Content**, and under **Default Views**, click **Landing page**. 2. Click **+ Add entry** and fill in the following: | Field | Input | |---|---| | Landing page title | Home | | Link | `/` | | Blog posts | Leave empty for now | | Product categories | Leave empty for now | | Seller information | Add the seller information entry created earlier | | Stripes | Click **+ Add component** and add a `Call to Action` and a `Product Grid` using the information below | **Call to Action:** | Field | Input | |---|---| | Heading | Main Headline for the page | | Body | We provide amazing products from outerwear, shoes, home goods, and more! Be sure to check out all the amazing products. | | Image | Add the Hygraph Logo asset | | Button | Click **+ Add Button**, enter `Shop Now` in the Text field and `https://google.com` in the URL field | **Product Grid:** | Field | Input | |---|---| | Headline | Picks of the month | | Products | Add relations to: blue running shoes, black leather shoes, necklace, headband, plaid shirt | 3. Click **Save & Publish**. Your homepage entry should look like this: ### Summer Campaign 1. Go to **Content**, and under **Default Views**, click **Landing page**. 2. Click **+ Add entry** and fill in the following: | Field | Input | |---|---| | Landing page title | Summer Campaign | | Link | summer-campaign | | Blog posts | Leave empty for now | | Product categories | Leave empty for now | | Seller information | Add the seller information entry created earlier | | Stripes | Click **+ Add component** and add a `Call to Action` and a `Product Grid` using the information below | **Call to Action:** | Field | Input | |---|---| | Heading | Get ready for summer with these amazing deals | | Body | The Summer Sale is in full swing! See the amazing summer deals we have listed below! | | Image | Leave empty | | Button | Leave empty | **Product Grid:** | Field | Input | |---|---| | Headline | Summer wear | | Products | Add relations to: blue running shoes, black leather shoes, necklace, headband, plaid shirt | 3. Click **Save & Publish**. ## Navigation The `Navigation` model uses the `Link` component built in lesson 4.1. Each `Nav Link` entry in the navigation maps to one link in the frontend navigation bar. 1. Go to **Content**, and under **Default Views**, click **Navigation**. 2. Click **+ Add entry** and fill in the following: | Field | Input | |---|---| | Nav ID | main | 3. Click **+ Add Link** to add the first Nav Link component and fill in: | Field | Input | |---|---| | Display Text | Clothes | | Page | Leave empty | | External URL | `/category/clothes` | 4. Click **+ Add another Link** and repeat for each of the following: **Shoes** | Field | Input | |---|---| | Display Text | Shoes | | Page | Leave empty | | External URL | `/category/shoes` | **Accessories** | Field | Input | |---|---| | Display Text | Accessories | | Page | Leave empty | | External URL | `/category/accessories` | **Decor** | Field | Input | |---|---| | Display Text | Decor | | Page | Leave empty | | External URL | `/category/decor` | **Summer Campaign** | Field | Input | |---|---| | Display Text | Summer Campaign | | Page | Click **Add existing document** and select the Summer Campaign landing page | | External URL | Leave empty | 5. Click **Save & Publish**. The navigation entry now contains all the links the frontend needs. These will be visible when the storefront is connected in lesson 8. ## What's next Or, go to the [Tutorial overview](/docs/getting-started/tutorial/tutorial-overview) for the full lesson list. --- # Content creation — additional practice Source: https://hygraph.com/docs/getting-started/tutorial/tutorial-create-content-additional-practice This page contains optional additional content for lesson 6.1. None of this is required for the frontend to connect and function. Some of the additional product entries are referenced in more advanced API Playground exercises in lesson 7.1. Content on this page depends on other content existing first. You cannot localize an alt text field that was never filled in. You cannot add related products that were never created. ## Assets Add alt text and captions to the following assets using the same process as in lesson 6.1: | Asset | Alt text | Caption | |---|---|---| | Green hoodie | Green hoodie | A comfortable green hoodie, 100% cotton | | Blue running shoes | Blue running shoes | A pair of lightweight blue running shoes | | Red urban shoes | Red urban shoes | A pair of stylish and modern red urban shoes | | Blue plaid shirt | Blue plaid shirt | A classic and warm blue plaid shirt | | Black leather shoes | Black leather shoes | A pair of elegant black leather shoes | | Coffee table | Coffee table | Coffee table with glass top and metal legs | | Coffee tray | Coffee tray | Metal coffee tray for two cups | | Floor lamp | Floor lamp | Floor lamp with paper top and bronze details | | Vase | Vase | Small metal vase | | Desk lamp | Desk lamp | An elegant silver lamp | | Bag | Green bag | A modern green leather bag | | Necklace | White necklace | A lovely white necklace with summer vibes | | Wallet | Black leather wallet | A black leather wallet with space for all your cards | | Fanny pack | Red fanny pack | A modern red fanny pack to carry your essentials | | Headband | Cotton headband | A soft white cotton headband | [Return to lesson 6.1 — Create content](/docs/getting-started/tutorial/tutorial-create-content#assets) ## Products Create the following additional product entries using the same process as in lesson 6.1. **Green Hoodie** | Field | Input | |---|---| | Product name | Green Hoodie | | Product slug | green-hoodie | | Product category | Select **Clothes**, **Sportswear**, and **New arrival** | | Product image | Select images with file name **green hoodie** | | Product description | This green hoodie combines comfort and style seamlessly, making it your go-to choice for a casual yet trendy look. Its soft fabric and versatile shade of green make it a wardrobe essential for all seasons. | | Product variant | Select **Clothing**, then Size `L` and Color `Green` | | Product price | 45 | **Desk lamp** | Field | Input | |---|---| | Product name | Desk lamp | | Product slug | desk-lamp | | Product category | Select **Decor** | | Product image | Select images with file name **desk lamp** | | Product description | This silver desk lamp illuminates your workspace with elegance and style. Its modern design and sleek silver finish make it a standout piece for your desk. | | Product variant | Select **Decor**, then Color `Silver` | | Product price | 35 | **Wallet** | Field | Input | |---|---| | Product name | Wallet | | Product slug | wallet | | Product category | Select **Accessories** | | Product image | Select images with file name **wallet** | | Product description | This black leather wallet combines functionality and style in a modern, compact design. With ample space for your cards and cash, it's the perfect accessory for carrying your essentials with elegance. | | Product variant | Select **Accessories**, then Color `Black` | | Product price | 39 | **Urban shoes** | Field | Input | |---|---| | Product name | Urban shoes | | Product slug | urban-shoes | | Product category | Select **Shoes** and **Urban** | | Product image | Select images with file name **red urban shoes** | | Product description | These red urban shoes effortlessly blend fashion with comfort, making a bold statement in any cityscape. The sleek design and cushioned insole ensure both style and ease for your urban adventures. | | Product variant | Select **Shoes**, then Size `Size_38` and Color `Red` | | Product price | 130 | **Colorful socks** | Field | Input | |---|---| | Product name | Colorful socks | | Product slug | colorful-socks | | Product category | Select **Clothes**, **Urban**, **Accessories**, and **New arrival** | | Product image | Select images with file name **colorful socks** | | Product description | These vibrant socks are designed to add a touch of joy to your everyday style. With eye-catching colors and a comfortable fit, they're the perfect choice to express your personality with every step. | | Product variant | Select **Accessories**, then Color `Red` | | Product price | 12 | **Floor lamp** | Field | Input | |---|---| | Product name | Floor lamp | | Product slug | floor-lamp | | Product category | Select **Decor** | | Product image | Select images with file name **floor lamp** | | Product description | This floor lamp seamlessly blends the warmth of paper with metal and wooden details, adding a modern and elegant touch to your space. Its unique design softly and harmoniously lights up your surroundings. | | Product variant | Select **Decor**, then Color `Bronze` | | Product price | 240 | **Coffee table** | Field | Input | |---|---| | Product name | Coffee table | | Product slug | coffee-table | | Product category | Select **Decor** | | Product image | Select images with file name **coffee table** | | Product description | This coffee table blends elegance and modernity with its glass surface and metal legs. Perfect to add a contemporary touch to your living space. | | Product variant | Select **Decor**, then Color `Silver` | | Product price | 199 | **Coffee tray** | Field | Input | |---|---| | Product name | Coffee tray | | Product slug | coffee-tray | | Product category | Select **Decor** | | Product image | Select images with file name **coffee tray** | | Product description | This metal coffee tray is crafted for two cups, adding a touch of industrial style to your coffee experience. Its sleek and durable design makes it a perfect addition to your morning routine. | | Product variant | Select **Decor**, then Color `Silver` | | Product price | 19 | **Vase** | Field | Input | |---|---| | Product name | Vase | | Product slug | vase | | Product category | Select **Decor** | | Product image | Select images with file name **vase** | | Product description | This small metal vase is versatile and stylish, perfect for displaying fresh flowers or storing bathroom items like makeup brushes. Its modern design easily complements any space. | | Product variant | Select **Decor**, then Color `Silver` | | Product price | 15 | **Bag** | Field | Input | |---|---| | Product name | Bag | | Product slug | bag | | Product category | Select **Accessories** | | Product image | Select images with file name **bag** | | Product description | This green leather bag is the perfect accessory to carry your belongings with style and elegance. Crafted from high-quality leather, its spacious and modern design is perfect for any occasion. | | Product variant | Select **Accessories**, then Color `Green` | | Product price | 52 | **Fanny pack** | Field | Input | |---|---| | Product name | Fanny pack | | Product slug | fanny-pack | | Product category | Select **Accessories** | | Product image | Select images with file name **fanny pack** | | Product description | This red fanny pack is the perfect accessory to carry your essentials comfortably and in style. Its vibrant color and modern design make it the ideal choice for your daily adventures. | | Product variant | Select **Accessories**, then Color `Red` | | Product price | 14 | [Return to lesson 6.1 — Create content](/docs/getting-started/tutorial/tutorial-create-content#products) ## Related products Add related product assignments to the following additional entries: | Product entry | Related products | |---|---| | Green Hoodie | colorful socks, blue running shoes, urban shoes, plaid shirt | | Desk lamp | floor lamp, coffee table, coffee tray, vase | | Wallet | bag, necklace, headband, fanny pack | | Urban shoes | colorful socks, blue running shoes, plaid shirt, black leather shoes | | Floor lamp | coffee table, coffee tray, vase, desk lamp | | Coffee table | floor lamp, coffee tray, vase, desk lamp | | Coffee tray | floor lamp, coffee table, vase, desk lamp | | Vase | floor lamp, coffee table, coffee tray, desk lamp | | Bag | wallet, necklace, headband, fanny pack | | Fanny pack | wallet, bag, necklace, headband | [Return to lesson 6.1 — Create content](/docs/getting-started/tutorial/tutorial-create-content#related-products) --- # 1.1 Design your content models Source: https://hygraph.com/docs/getting-started/tutorial/tutorial-design-content-models In this lesson, you will map the content structure the project requires before building anything in the schema editor. This lesson has no steps to complete in Hygraph. The decisions made here determine every field, reference, and component configured in the lessons that follow. Read [Content modeling in Hygraph](/docs/core-concepts/content-modeling/content-modeling-in-hygraph) to understand the underlying concepts first. ## The project The e-commerce project sells wearables and home goods. Before opening the schema editor, you need to map the content structure against what the store must do. ![Content modeling](/images/docs/getting-started/content-modeling-matters.png) The store has four goals: - **Display product listings with enough detail for a buyer to make a decision**: image, size, color, price, description, and reviews. - **Organize products into categories**, where one product can belong to multiple categories and one category can contain multiple products. - **Display related products on each product page** to encourage further exploration. - **Support a landing page, a blog, seller information, and a navigation structure**. Reviews come from an external source. You will not store them in Hygraph. Instead, you will fetch them from a remote API using Content Federation. ## Proposed content flow Based on those goals, here is what the schema will contain. ![Project structure](/images/docs/getting-started/hygraph-project-structure.png) **Models** | Model | Purpose | |---|---| | `Product` | Product listings with name, slug, images, description, price, and variant | | `Product category` | Categories that products can be assigned to | | `Blog post` | Articles promoting products | | `Seller information` | Business details referenced from the landing page | | `Landing page` | Flexible pages built from component sections | | `Navigation` | Site navigation built from Link components | **Components** | Component | Purpose | |---|---| | Product Variant | Modular component for product type selection (Clothing, Shoes, Accessories, or Decor) | | Clothing | Size and color fields for clothing items | | Shoes | Size and color fields for footwear | | Accessories | Color field for accessories | | Decor | Color field for decor items | | Related products | A set of product references for related product sections | | Call to Action | Heading, body, image, and button for CTA sections | | Button | Text and URL for use inside CTA components | | Product Grid | Headline, description, and product references for grid sections | | Link | Display text, URL, and page reference for navigation links | **Product Variant as a modular component, not separate models.** The store sells clothing, shoes, accessories, and decor. Each product type requires different fields. Shoes need a shoe size, clothing needs a clothing size, accessories need neither. Rather than creating four separate product models, the schema uses a single Product model with a modular component field. The component presents only the relevant fields based on the product type selected in the content editor. **Enumerations** | Enumeration | Values | |---|---| | Clothes size | XS, S, M, L, XL | | Shoes size | Size_35 through Size_44 | | Wearable items color | Red, Green, Blue, Black, White | | Decor items color | Gold, Silver, Bronze, Cobalt | **Remote source** | Source | Purpose | |---|---| | HyDemoAPI | Fetches product reviews from an external REST API. Configured as a Remote Field on the Product model and a Top-level Remote Field on the Query model. | **Reviews as a remote field, not a model.** Reviews exist in a third-party system. Migrating them into Hygraph would duplicate data and create a synchronization problem. A remote source connects to the origin directly. Review data appears in the API response alongside Hygraph content, with no migration required. ## What's next Or, go to the [Tutorial overview](/docs/getting-started/tutorial/tutorial-overview) for the full lesson list. --- # Lesson 3 - Enumerations Source: https://hygraph.com/docs/getting-started/tutorial/tutorial-enumerations-overview This section covers creating the enumerations the product variant components depend on. An enumeration defines a fixed set of allowed values at the schema level. The enumerations configured here, `Clothes size`, `Shoes size`, `Wearable items color`, and `Decor items color`, power the size and color dropdowns in the content editor in later lessons. They need to exist in the schema before the components that use them can be built. ## How enumerations work An enumeration defines a fixed set of allowed values for a field. When a field is typed to an enumeration, the API enforces the allowed values at the schema level. A product entry cannot be published with a color value of "navy" if the enumeration only contains Red, Green, Blue, Black, and White. This is different from a free-text field, where any value is accepted, and different from a reference, where values are editor-managed content entries. Use an enumeration when the set of valid values is fixed, controlled by the schema owner, and unlikely to be extended by editors. [Learn more about enumerations](/docs/developer-guides/schema/using-enumerations). Use a reference when the list of values needs to be managed as content entries by editors. Use a taxonomy when the values are hierarchical. Enumerations are for flat, schema-controlled value sets that change infrequently. ## Lessons in this section | Lesson | What you will do | |---|---| | [3.1 Configure enumerations](/docs/getting-started/tutorial/tutorial-configure-enumerations) | Create enumerations for product size and color values that the product variant components will use | ## What's next Or, go to the [Tutorial overview](/docs/getting-started/tutorial/tutorial-overview) for the full lesson list. --- # Lesson 8 - Frontend connection Source: https://hygraph.com/docs/getting-started/tutorial/tutorial-frontend-overview This section covers setting up the Next.js e-commerce starter, connecting it to your Hygraph project, and configuring a preview URL for draft content. The work in this section is configuration only. The starter handles all the frontend code for routing, components, queries, and styling. You need to provide the environment variables that tell the starter where to find your Hygraph project and how to authenticate. The starter uses two different ways to access your Hygraph content: - **The Content API** serves public visitors. It only returns content that has been explicitly published. - **A Permanent Auth Token (PAT)** grants the frontend access to draft content for preview purposes. ## Lessons in this section | Lesson | What you will do | |---|---| | [8.1 Set up the frontend starter](/docs/getting-started/tutorial/tutorial-frontend-setup) | Clone the Next.js e-commerce starter, install dependencies, and orient yourself to the repo structure | | [8.2 Connect your storefront](/docs/getting-started/tutorial/tutorial-connect-storefront) | Configure Content API permissions, create a PAT, and add the environment variables that connect the starter to your Hygraph project | | [8.3 Configure a preview URL](/docs/getting-started/tutorial/tutorial-preview-url) | Add a preview button to the Product model content editor sidebar so you can preview draft product pages before publishing | ## What's next Or, go to the [Tutorial overview](/docs/getting-started/tutorial/tutorial-overview) for the full lesson list. --- # Lesson 8.1 - Set up the frontend starter Source: https://hygraph.com/docs/getting-started/tutorial/tutorial-frontend-setup In this lesson, you will clone the Hygraph Next.js e-commerce starter and install its dependencies. By the end, the starter will be running locally and ready to be connected to your Hygraph project in the next lesson. This lesson clones a pre-built starter. If you want to understand how to create a Next.js project from scratch, see the Next.js documentation. Creating a Next.js app from scratch is out of scope for this tutorial. ## Clone the starter 1. Open your terminal and navigate to the directory where you want the project. 2. Run the following command to clone the starter: ```bash git clone https://github.com/hygraph/hygraph-next-commerce-starter.git ``` 3. Navigate into the project directory: ```bash cd hygraph-next-commerce-starter ``` 4. Install the package dependencies: ```bash npm install ``` Your project file tree should look like this: ![Next file tree](/images/docs/getting-started/next-file-tree.png) ## What's in the starter The starter is a Next.js 13 App Router project with Tailwind CSS. Three directories contain the code that powers the storefront: - **`app/`**: Next.js page components and dynamic routes. The product page at `app/products/[slug]/page.js` and the landing page at `app/[slug]/page.js` are the two routes your Hygraph content populates. - **`utils/`**: GraphQL query functions. The queries you wrote in lesson 7.1 are used here to fetch content from Hygraph. Each query function maps to a page or component in the `app/` directory. - **`components/`**: UI components for navigation, product grids, CTAs, landing page sections, and product reviews. ## What's in `.env.sample` The starter includes a `.env.sample` file in the project root. It contains the three environment variable placeholders you will populate in lessons 8.2 and 8.3: ```bash HYGRAPH_ENDPOINT= HYGRAPH_DEV_AUTH_TOKEN= HYGRAPH_PREVIEW_SECRET= ``` Do not populate these yet. Lesson 8.2 explains what each variable controls and where to find the values. ## What's next Or, go to the [Tutorial overview](/docs/getting-started/tutorial/tutorial-overview) for the full lesson list. --- # Lesson 6.2 - Localize content Source: https://hygraph.com/docs/getting-started/tutorial/tutorial-localize-content In this lesson, you will add a Spanish locale to the project and write localized content for the Summer Campaign and the product entries. While you were building the schema in lesson 1.2, certain fields were marked with the **Localize field** checkbox. Only those fields are available for localization in the content editor. Fields that were not marked are not localizable and display only the default English value. Adding a locale at the project level is what makes the language toggle available at the entry level. Without this step, the Spanish tab does not appear in the content editor. ## Add a locale 1. Navigate to **Project settings > General > Locales**. 2. Use the dropdown to select **Spanish**. 3. Click **Add**. The Spanish locale is now added to the project and the language toggle appears in the content editor for all localizable fields. ## Localized content ### Summer Campaign 1. Open the **Summer Campaign** landing page entry in the content editor. 2. Under **Localizations**, click **+** next to **Spanish** to add fields for the Spanish locale. 3. Fill in the following localized fields and click **Save & Publish**: | Field | Translated content | |---|---| | Landing page title | Campaña de verano | | Stripes > Call to Action > Heading | Prepárate para el verano con estas increíbles ofertas | | Stripes > Call to Action > Body | ¡Las rebajas de verano están en pleno apogeo! Mira las increíbles ofertas de verano que tenemos a continuación! | | Stripes > Product Grid > Headline | Ropa de verano | ### Products Repeat the same process for the following product entries: **Blue running shoes** | Field | Translated content | |---|---| | Product name | Zapatillas para correr azules | | Product description | Estas zapatillas de running azules ofrecen tanto forma como función, brindando un apoyo excepcional y estilo para tus actividades deportivas. El material transpirable y la suela acolchada garantizan una experiencia cómoda kilómetro tras kilómetro. | **Plaid shirt** | Field | Translated content | |---|---| | Product name | Camisa a cuadros | | Product description | Esta camisa a cuadros emana un encanto atemporal, ideal para un día casual fuera o una reunión relajada por la tarde. El patrón clásico a cuadros y el ajuste cómodo la convierten en una prenda básica para un estilo sin esfuerzo. | **Black leather shoes** | Field | Translated content | |---|---| | Product name | Zapatos de cuero negro | | Product description | Estos zapatos de cuero negro combinan de manera impecable la sofisticación con la durabilidad, convirtiéndolos en una elección versátil tanto para ocasiones formales como para uso diario. El diseño elegante y el cuero de alta calidad garantizan un atractivo atemporal que complementa cualquier conjunto. | **Necklace** | Field | Translated content | |---|---| | Product name | Collar | | Product description | Este collar blanco está elaborado con partes de caracoles de mar, evocando la esencia relajada y playera. Perfecto para quienes buscan un toque marino en su estilo diario. | **Headband** | Field | Translated content | |---|---| | Product name | Vincha | | Product description | Esta vincha de algodón blanco brinda un toque elegante y sofisticado a tu peinado. Ideal para completar tu look con un toque de moda y comodidad. | ## What's next Or, go to the [Tutorial overview](/docs/getting-started/tutorial/tutorial-overview) for the full lesson list. --- # Full Tutorial - Build Your First eCommerce Project Source: https://hygraph.com/docs/getting-started/tutorial/tutorial-overview This tutorial takes you through a complete Hygraph project: an e-commerce store selling wearables and home goods. By the end, you will have a working schema, real content, a queryable API, and a connected Next.js storefront. ![Getting started project preview](/images/docs/getting-started/project-preview.png) Every decision in this tutorial is one you will face on a real project. The schema choices you make in lessons 1 through 5 determine what you can query in lesson 7 and what your frontend renders in lesson 8. ## Choose your path Your starting point is a cloned Hygraph project. You can choose your own path through the tutorial. **Option 1: Build from scratch** This clone includes navigation structure and image assets. You will build the schema, content, API configuration, and frontend connection yourself, step by step. Choose this path if you want to understand how every part of a Hygraph project is constructed and why. **Requirements** - Hygraph account - Basic GraphQL knowledge - Basic understanding of [content modeling](/docs/core-concepts/content-modeling/content-modeling-overview) - Code editor, such as VS Code - Current version of Node.js - Netlify or Vercel account (required for lessons 8 and 9) - Growth plan or higher, or a [30-day trial](/docs/getting-started/update-billing), for lessons 5.1 through 5.3 **Option 2: Explore a complete project** This clone includes the complete Hygraph project: all models, components, enumerations, content entries, and localized content already built. You will skip straight to querying and frontend connection. Choose this path if you want to practice API queries, mutations, and frontend integration without building the schema yourself. **Requirements** - Hygraph account - Basic GraphQL knowledge - Basic understanding of [content modeling](/docs/core-concepts/content-modeling/content-modeling-overview) - Growth plan or higher, or a [30-day trial](/docs/getting-started/update-billing), for remote sources ## Clone the project The cloning process is the same for both options. ![Clone project screen](/images/docs/getting-started/clone-project-screen.png) The project already has a name, but you can give it a new name. You can also optionally add a description, select a region, and click **Clone now**. Cloning takes a couple of minutes. When it finishes, you land on your project homepage. ## What's already in your project This section is for Option 2 readers only. If you chose Option 1, move on to the lesson overview below. Your cloned project is fully built. Here is what it contains. **Schema** | Type | What's included | |---|---| | Models | Product, Product category, Blog post, Seller information, Landing page, Navigation | | Components | Related Products, Product variant, Clothing, Shoes, Accessories, Decor, Button, Call to Action, Product Grid, Link | | Enumerations | Clothes size, Shoes size, Wearable items color, Decor items color | | Remote source | HyDemoAPI, a Hygraph-maintained REST API used to fetch product reviews | | Remote fields | Reviews field on the Product model; Reviews top-level field on the Query model | **Content** | Type | What's included | |---|---| | Products | 9 published entries with images, variants, related products, and category assignments | | Product categories | 7 published entries | | Landing pages | Homepage and Summer Campaign, both with localized Spanish content | | Navigation | Main navigation with category and campaign links | | Seller information | 1 published entry | | Assets | All product images with alt text and captions | Your project does not include API tokens, environment variable configuration, a connected frontend, or webhooks. You will set those up in lessons 8 and 9. **Your path through the tutorial** 1. Query your content in the API Playground. 2. Run mutations. 3. Set up the Next.js frontend starter. 4. Connect Hygraph to your local frontend. 5. Configure a preview URL for draft content. 6. Set up a webhook to trigger frontend deploys on publish. ## Lesson overview The full lesson sequence is below. Option 2 readers start at [lesson 7.1](/docs/getting-started/tutorial/tutorial-write-queries). ### Lesson 1 — Content models | # | Lesson | What you will be able to do | |---|---|---| | 1.1 | [Design your content models](/docs/getting-started/tutorial/tutorial-design-content-models) | Sketch your model structure and understand how fields, models, and schema relate to each other | | 1.2 | [Build your content models](/docs/getting-started/tutorial/tutorial-build-content-models) | Create your base models and fields in the Hygraph schema editor | ### Lesson 2 — References | # | Lesson | What you will be able to do | |---|---|---| | 2.1 | [Configure reference fields](/docs/getting-started/tutorial/tutorial-configure-reference-fields) | Connect models to each other using one-to-one, one-to-many, and many-to-many references | ### Lesson 3 — Enumerations | # | Lesson | What you will be able to do | |---|---|---| | 3.1 | [Configure enumerations](/docs/getting-started/tutorial/tutorial-configure-enumerations) | Create predefined value sets that appear as dropdowns in the content editor | ### Lesson 4 — Components | # | Lesson | What you will be able to do | |---|---|---| | 4.1 | [Build components](/docs/getting-started/tutorial/tutorial-build-components) | Create reusable field sets for product variants, CTAs, related products, and navigation links | | 4.2 | [Add components to your models](/docs/getting-started/tutorial/tutorial-add-components-to-models) | Attach your components to models as basic and modular component fields | ### Lesson 5 — Remote Sources | # | Lesson | What you will be able to do | |---|---|---| | 5.1 | [Add a Remote Source](/docs/getting-started/tutorial/tutorial-add-remote-source) | Connect an external REST API to your Hygraph schema | | 5.2 | [Remote Fields](/docs/getting-started/tutorial/tutorial-add-remote-fields) | Fetch remote data within the context of a specific model entry | | 5.3 | [Top-level Remote Fields](/docs/getting-started/tutorial/tutorial-add-top-level-remote-fields) | Fetch remote data outside the context of any model and serve it alongside Hygraph content in a single API call | ### Lesson 6 — The Content Editor | # | Lesson | What you will be able to do | |---|---|---| | 6.1 | [Create content](/docs/getting-started/tutorial/tutorial-create-content) | Create and publish products, categories, landing pages, and navigation entries | | 6.2 | [Localize content](/docs/getting-started/tutorial/tutorial-localize-content) | Add a locale and write localized content for selected fields | | 6.3 | [Understand content stages](/docs/getting-started/tutorial/tutorial-content-stages) | Learn how `DRAFT` and `PUBLISHED` content stages affect what the Content API returns and what the frontend displays | ### Lesson 7 — The API Playground | # | Lesson | What you will be able to do | |---|---|---| | 7.1 | [Exercises: Queries](/docs/getting-started/tutorial/tutorial-write-queries) | Write queries for models, references, components, and remote fields | | 7.2 | [Exercises: Mutations](/docs/getting-started/tutorial/tutorial-write-mutations) | Create, update, publish, and delete content entries programmatically | ### Lesson 8 — Frontend Connection | # | Lesson | What you will be able to do | |---|---|---| | 8.1 | [Set up the frontend starter](/docs/getting-started/tutorial/tutorial-frontend-setup) | Clone and configure the Next.js e-commerce starter locally | | 8.2 | [Connect your storefront](/docs/getting-started/tutorial/tutorial-connect-storefront) | Configure API access permissions, create a PAT, and connect Hygraph to your local frontend | | 8.3 | [Preview URL](/docs/getting-started/tutorial/tutorial-preview-url) | Add a preview button to the content editor sidebar to preview draft content before publishing | ### Lesson 9 — Webhooks | # | Lesson | What you will be able to do | |---|---|---| | 9.1 | [Configure webhooks](/docs/getting-started/tutorial/tutorial-configure-webhooks) | Set up a webhook that triggers a frontend rebuild whenever content is published | ## What's next OR --- # Lesson 8.3 - Configure a preview URL Source: https://hygraph.com/docs/getting-started/tutorial/tutorial-preview-url In this lesson, you will configure a preview URL for the `Product` model. Once configured, every product entry in the content editor displays an **Open live preview** button in its sidebar. Clicking it opens a preview of that product page in your local frontend using the draft content token configured in lesson 8.2. The preview URL uses a secret key to prevent unauthorized access to draft content. Only requests that include the correct secret can trigger a draft preview. Generate a strong secret and do not commit it to version control. ## Configure the preview URL 1. Navigate to the **Schema** and select the **Product** model. 2. Click the **Sidebar** tab. 3. Select the **Preview** widget. 4. Provide the following details: - **Preview Name**: Product preview - **URL template**: `http://localhost:3000/api/draft?slug={productSlug}&model=products&secret=YOUR_SECRET_HERE` `{productSlug}` is handlebars notation. Hygraph replaces it with the `productSlug` field value of the entry being previewed. The API ID `productSlug` was set when you created the Product Slug field in lesson 1.2. 5. Click **Save**. ## Add the preview secret 1. Generate a secret key using a key generator of your choice. Use a strong random string. 2. Replace `YOUR_SECRET_HERE` in the URL template with the generated secret. 3. Open `.env.local` and add the secret as `HYGRAPH_PREVIEW_SECRET`: ```bash HYGRAPH_PREVIEW_SECRET=YOUR_SECRET_HERE ``` 4. Restart the local development server to load the new environment variable: ```bash npm run dev ``` ## Your complete `.env.local` At this point, all three environment variables should be populated. Your `.env.local` file should look like this: ```bash HYGRAPH_ENDPOINT=YOUR_CONTENT_API_ENDPOINT_HERE HYGRAPH_DEV_AUTH_TOKEN=YOUR_DRAFT_TOKEN_HERE HYGRAPH_PREVIEW_SECRET=YOUR_SECRET_HERE ``` If any of the three are missing or incorrect, the preview will not work. ## Verify the preview URL 1. Navigate to the **Content editor** and open any product entry. 2. Click **Open live preview**. The product page should open in a new browser tab at `http://localhost:3000` showing the draft version of that product. ## What's next Or, go to the [Tutorial overview](/docs/getting-started/tutorial/tutorial-overview) for the full lesson list. --- # Lesson 7 - Queries and mutations Source: https://hygraph.com/docs/getting-started/tutorial/tutorial-queries-mutations-overview This section covers querying and mutating content in the Hygraph API Playground. The API Playground is not a demo environment. It queries the same Content API your frontend will use in lesson 8. Every query you run here returns real data from your project, and every mutation makes a real change. The patterns practiced in these two lessons are the same ones your frontend code will use. The exercises are structured in the same order the schema was built: basic model queries first, then references, then components, then remote data. If a query returns unexpected results, it is a signal about the schema or content, not a problem with the query itself. By the end of lesson 7.2, you will have: - Queried all major schema features: models, references, components, remote fields, and top-level remote fields - Created, updated, published, unpublished, and deleted a product entry programmatically - Confirmed that every schema decision made in lessons 1 through 5 is reflected in the API ## Lessons in this section | Lesson | What you will do | |---|---| | [7.1 Write queries](/docs/getting-started/tutorial/tutorial-write-queries) | Run queries for models, references, components, remote fields, and top-level remote fields in the API Playground | | [7.2 Write mutations](/docs/getting-started/tutorial/tutorial-write-mutations) | Create, update, publish, unpublish, and delete a product entry programmatically using the Product model mutations | For the complete Content API reference, see [Queries](/docs/api-reference/content-api/queries) and [Mutations](/docs/api-reference/content-api/mutations). ## What's next Or, go to the [Tutorial overview](/docs/getting-started/tutorial/tutorial-overview) for the full lesson list. --- # Lesson 2 - References Source: https://hygraph.com/docs/getting-started/tutorial/tutorial-references-overview This section covers adding reference fields to the models built in lesson 1.2. References connect models to each other. The connections you configure here are what make it possible to query a product and receive its categories in a single API call. Without references, a product entry has no connection to its categories, and a landing page has no connection to its blog posts. This is why references are a critical part of the schema. By the end of lesson 2.1, the following model relationships are configured: - Products connected to Product categories (many-to-many, two-way) - Landing page connected to Blog posts, Product categories, and Seller information (one-way) ## How references work The way you configure reference fields determines the following: **Type** controls which models can be referenced. Selecting a single model means only that model's entries can be connected. Selecting multiple models creates a union reference. **Direction** controls which side of the relationship can be queried. A one-way reference allows querying only from the model where the field lives. A two-way reference adds a reverse field to the referenced model, so you can query from either side. **Cardinality** controls how many entries can be connected. The combination of direction and cardinality produces four reference patterns: | Pattern | What it means | When to use it | |---|---|---| | One-to-one | One entry connects to one entry | A landing page linked to one seller information entry | | One-to-many | One entry connects to many entries | A landing page linked to multiple blog posts | | Many-to-many | Many entries connect to many entries | Products assigned to multiple categories | | Many-to-one | Many entries connect to one entry | Multiple landing pages linked to one seller information entry | The references created in this lesson impact the queries in lesson 7. For the complete reference field documentation, see [References](/docs/developer-guides/schema/references). ## Lessons in this section | Lesson | What you will do | |---|---| | [2.1 Configure reference fields](/docs/getting-started/tutorial/tutorial-configure-reference-fields) | Add reference fields to the `Product` and `Landing page` models and configure their type, direction, and cardinality | ## What's next Or, go to the [Tutorial overview](/docs/getting-started/tutorial/tutorial-overview) for the full lesson list. --- # Lesson 5 - Remote sources Source: https://hygraph.com/docs/getting-started/tutorial/tutorial-remote-sources-overview This section covers connecting the e-commerce project to an external REST API and making its data queryable through the Hygraph API alongside native Hygraph content. Remote sources require a Growth plan or higher, or a [30-day trial](/docs/getting-started/update-billing). Without this section, the schema can only return data stored in Hygraph. After completing the lessons in this section, a single API call to Hygraph returns both Hygraph content and external review data. No separate API request is needed, and no data is migrated or duplicated. The three lessons build on each other in sequence. The remote source must exist before adding remote fields. Remote fields must exist before the top-level remote field can be tested against real data. By the end of lesson 5.3, the project schema includes: - A REST remote source connected to the Hygraph demo API (`HyDemoAPI`) - A remote field on the `Product` model that fetches reviews scoped to each product entry - A top-level remote field on the `Query` model that fetches reviews independently of any product entry ## Lessons in this section | Lesson | What you will do | |---|---| | [5.1 Add a remote source](/docs/getting-started/tutorial/tutorial-add-remote-source) | Connect the HyDemoAPI REST endpoint to the project schema and define the custom types Hygraph needs to query it | | [5.2 Add remote fields](/docs/getting-started/tutorial/tutorial-add-remote-fields) | Add a remote field to the `Product` model to fetch review data scoped to each product entry | | [5.3 Add top-level remote fields](/docs/getting-started/tutorial/tutorial-add-top-level-remote-fields) | Add a top-level remote field to the `Query` model and run a test query to confirm the remote source is working | For the complete remote sources documentation, see [Remote Sources](/docs/developer-guides/remote-data/overview). ## What's next Or, go to the [Tutorial overview](/docs/getting-started/tutorial/tutorial-overview) for the full lesson list. --- # Lesson 9 - Webhooks Source: https://hygraph.com/docs/getting-started/tutorial/tutorial-webhooks-overview This section covers configuring a webhook that keeps the deployed storefront in sync with published content. A deployed storefront is a static build created at a specific point in time. When you publish content in Hygraph, the live site does not automatically update. Setting up a webhook closes that gap. When content moves to the `PUBLISHED` stage, Hygraph sends a POST request to a build hook URL on your deployment platform, which triggers a new build and deploys the latest published content. By the end of lesson 9.1, every time content is published in your Hygraph project, the storefront automatically rebuilds and deploys. ## Lessons in this section | Lesson | What you will do | |---|---| | [9.1 Configure webhooks](/docs/getting-started/tutorial/tutorial-configure-webhooks) | Create a webhook in Hygraph that sends a build trigger to Netlify or Vercel whenever content is published | For the complete webhooks documentation, see [Webhooks](/docs/developer-guides/webhooks/webhooks-overview). ## What's next Or, go to the [Tutorial overview](/docs/getting-started/tutorial/tutorial-overview) for the full lesson list. --- # Wrap-up Source: https://hygraph.com/docs/getting-started/tutorial/tutorial-wrap-up You built a complete, deployed e-commerce website backed by Hygraph. The schema, content, API, storefront, preview configuration, and deployment automation you configured in this tutorial are the same patterns used in production Hygraph projects. ## What you can now do **Design a content architecture from business goals.** You started with four store requirements: product listings, categories, landing pages, and reviews, and translated them into a schema of models, components, enumerations, and remote sources. You can apply this planning process to any content-driven project. **Build a schema in layers.** You built the schema across four sections: models, references, components, and remote sources, because models are interconnected and some connections can only be made after their dependencies exist. This layered approach applies to any Hygraph project regardless of complexity. **Use Content Federation to query external data.** You connected an external REST API to the Hygraph schema using a remote source, added a remote field scoped to individual product entries, and added a top-level remote field that returns external data independently of any content entry. A single API call now returns Hygraph content and external review data together. **Query and mutate content programmatically.** You wrote queries for models, references, modular components, and remote fields using the Hygraph API Playground. You created, updated, published, unpublished, and deleted a content entry using mutations. These are the same operations your frontend uses. **Connect a Next.js frontend to Hygraph.** You configured Content API permissions, created a PAT for draft content access, connected a Next.js project using environment variables, and confirmed published content renders in the browser. **Automate deployments on publish.** You configured a webhook that sends a POST request to your deployment platform whenever a `Product` entry is published, triggering an automatic rebuild without manual intervention. ## Best practices **Start with business goals, not schema fields.** The models in this tutorial came from four store requirements, not from a list of fields someone wanted to store. Knowing what the store must do is what determined the schema structure. **Name models and fields for collaborators, not for yourself.** API IDs like `productSlug`, `categoryName`, and `businessDescription` are readable by any developer or editor who joins the project later. Ambiguous names like `field1` or `titleText` create friction that compounds over time. **Build schemas in layers.** Models are interconnected. Build base models first, then references, then components, then remote fields. Trying to configure everything at once leads to circular dependencies and incomplete field definitions. **Localize at the schema level, not as an afterthought.** Deciding which fields need localization at schema design time is cheaper than restructuring later. ## What to build next Before going deeper on any specific Hygraph feature, consider adding an SEO component to your project. It is something every production project needs. | Field | Type | Notes | |---|---|---| | SEO title | Single line text | Overrides the page title in search results | | SEO description | Single line text | Appears under the page title in search results | | SEO image | Asset picker | Cover image for social sharing. Use a named, compressed image with clear alt text. | | Canonical URL | Slug | Leave blank to default to the page URL. Prevents duplicate content penalties. | Attach the SEO component to the `Product`, `Landing page`, and `Blog post` models using a basic component field. ## Continue your learning ### Schema and content modeling | Resource | What it covers | |---|---| | [Content modeling](/docs/core-concepts/content-modeling/content-modeling-in-hygraph) | The full theory behind content modeling in Hygraph: models, fields, scalars, and system artifacts | | [Components](/docs/developer-guides/schema/components) | Component types, nested components, copy/paste, the 50-instance UI limit, and querying | | [Components or references?](/docs/developer-guides/schema/components-or-references) | How to decide between a component field and a reference field | | [References](/docs/developer-guides/schema/references) | All reference types, directions, and cardinality options | | [Enumerations](/docs/developer-guides/schema/using-enumerations) | Enumeration configuration and API behavior | | [Taxonomies](/docs/developer-guides/schema/taxonomies) | Hierarchical classification structures and the `descendants_of` filter | | [Field types](/docs/api-reference/schema/field-types) | API reference for all field types, input types, and filter options | | [Field configuration](/docs/api-reference/schema/field-configuration) | Settings, validations, and advanced options for every field | ### API and content operations | Resource | What it covers | |---|---| | [Queries](/docs/api-reference/content-api/queries) | Full query reference: filters, pagination, ordering, and localization | | [Mutations](/docs/api-reference/content-api/mutations) | Full mutation reference: create, update, publish, unpublish, delete | | [Content stages](/docs/api-reference/content-api/content-stages) | Default and custom content stages, and how to use them in workflows | | [Content localization](/docs/api-reference/content-api/localization) | Full localization reference beyond what the tutorial covers | | [Assets](/docs/api-reference/assets/assets-overview) | Asset upload, management, and transformations | | [Remote Sources](/docs/developer-guides/remote-data/overview) | Full reference for remote sources, remote fields, and top-level remote fields | ### Deployment and automation | Resource | What it covers | |---|---| | [Webhooks](/docs/developer-guides/webhooks/webhooks-overview) | Webhook configuration, triggers, logs, and retry behavior | | [Deploy to Vercel with webhooks](/docs/developer-guides/webhooks/trigger-static-build) | Step-by-step Vercel deployment automation | | [Hygraph-Netlify integration](/docs/integrations/connect-netlify) | Manual redeploy button and deployment status indicator in the content editor | | [Content Workflows](/docs/developer-guides/project/content-workflows) | Custom review and approval workflows between DRAFT and PUBLISHED | --- # Lesson 7.2 - Write mutations Source: https://hygraph.com/docs/getting-started/tutorial/tutorial-write-mutations In this lesson, you will use mutations to create, update, publish, unpublish, and delete a product entry. The mutations follow a complete content lifecycle: create it, change it, publish it, unpublish it, remove it. After each destructive mutation, you will run a quick query to confirm the change took effect. Running a verification query after a mutation is not just a tutorial step. It is the correct practice for any destructive or state-changing API operation. Hygraph automatically generates five mutations for each model when it is created. For the Product model, those are `createProduct`, `updateProduct`, `publishProduct`, `unpublishProduct`, and `deleteProduct`. They are used here in lifecycle order. ## `createProduct` This mutation creates a new product entry with a name, slug, price, product variant, related product, category assignment, and a placeholder image asset. ```graphql mutation MyMutation { createProduct( data: { productName: "My latest product" productSlug: "my-latest-product" productPrice: 59.99 productVariant: { create: { productType: { create: { Clothing: { size: M, color: Green } } } } } relatedProducts: { create: { title: "Related Products" products: { connect: { productSlug: "plaid-shirt" } } } } productCategories: { connect: { slug: "clothes" } } productImage: { create: { handle: "", fileName: "" } } } ) { productName productPrice } } ``` ```json { "data": { "createProduct": { "productName": "My latest product", "productPrice": 59.99 } } } ``` The mutation uses nested `create` blocks to configure the product variant and related products inline. The `products` connect uses `plaid-shirt`, one of the five core products from lesson 6.1. The `productCategories` field uses `connect` to link to an existing category entry rather than creating a new one. Open the **Content editor** and navigate to the **Product** model. The new entry appears there in the `DRAFT` stage. ![createProduct mutation result in the content editor](/images/docs/getting-started/createproduct-mutations-example.png) ## `updateProduct` This mutation changes the `productName` of the entry created above. The `where` clause uses `productSlug` as the unique identifier because it was configured to be unique in lesson 1.2. ```graphql mutation MyMutation { updateProduct( data: { productName: "Updated Product" } where: { productSlug: "my-latest-product" } ) { productSlug productName } } ``` ```json { "data": { "updateProduct": { "productSlug": "my-latest-product", "productName": "Updated Product" } } } ``` ![updateProduct mutation result](/images/docs/getting-started/updateproduct-mutations-example1.png) The `updateProduct` mutation works on any entry, not only newly created ones. To demonstrate, update the Black leather shoes entry: ```graphql mutation MyMutation { updateProduct( data: { productName: "My black leather shoes" } where: { productSlug: "black-leather-shoes" } ) { productSlug productName } } ``` ```json { "data": { "updateProduct": { "productSlug": "black-leather-shoes", "productName": "My black leather shoes" } } } ``` ![updateProduct mutation second example](/images/docs/getting-started/updateproduct-mutations-example2.png) ## `publishProduct` The entry created above is in `DRAFT`. This mutation promotes it to `PUBLISHED` so it is visible when querying the `PUBLISHED` stage. ![Entry in DRAFT stage before publishing](/images/docs/getting-started/publishProduct-mutations-draftmode.png) ```graphql mutation MyMutation { publishProduct(where: { productSlug: "my-latest-product" }, to: PUBLISHED) { stage productName } } ``` Open the content editor to confirm the entry is now published. ![Entry in PUBLISHED stage after publishing](/images/docs/getting-started/publishProduct-mutations-publishedmode.png) ## `unpublishProduct` This step is optional. Skip to `deleteProduct` to continue the lifecycle. This mutation returns the entry to `DRAFT` only. ```graphql mutation MyMutation { unpublishProduct( where: { productSlug: "my-latest-product" } from: PUBLISHED ) { stage productName } } ``` ```json { "data": { "unpublishProduct": { "stage": "DRAFT", "productName": "Updated Product" } } } ``` ![Entry returned to DRAFT after unpublishing](/images/docs/getting-started/publishProduct-mutations-draftmode2.png) ## `deleteProduct` This mutation permanently deletes the entry. Deletion cannot be undone. ```graphql mutation DeleteMutation { deleteProduct(where: { productSlug: "my-latest-product" }) { productName } } ``` ```json { "data": { "deleteProduct": { "productName": "Updated Product" } } } ``` ## Verify the deletion Run this query to confirm the entry no longer exists. ```graphql query CheckDeletion { products { productName } } ``` ```json { "data": { "products": [ { "productName": "My black leather shoes" }, { "productName": "Blue running shoes" }, { "productName": "Plaid shirt" }, { "productName": "Necklace" }, { "productName": "Headband" } ] } } ``` Your response reflects only the product entries you created. If you completed the additional practice in lesson 6.1, more entries will appear. The Black leather shoes entry may show the updated name "My black leather shoes" from the updateProduct mutation above. The deleted entry does not appear. This verify-after-mutate pattern applies to any mutation in any project. Run a query immediately after a destructive operation to confirm the result. The project now has a queryable API, published content, and a confirmed remote source connection. The next step is connecting a frontend to display this content in a storefront. ## What's next Or, go to the [Tutorial overview](/docs/getting-started/tutorial/tutorial-overview) for the full lesson list. --- # Lesson 7.1 - Write queries Source: https://hygraph.com/docs/getting-started/tutorial/tutorial-write-queries In this lesson, you will run queries against the project's Content API in the API Playground. The exercises follow the same order the schema was built: basic model queries first, then references, then components, then remote data. Each exercise proves that a specific set of schema decisions works as configured. Navigate to the **API Playground** in your project sidebar to get started. ## How queries are generated Hygraph automatically generates queries for each model when it is created. Two queries are generated per model, named after the model's **API ID** and **Plural API ID**. The API ID fetches a single entry. The Plural API ID fetches multiple entries. The **Product category** model is a good example. In the API Playground tree, `productCategory` fetches one category entry and `productCategories` fetches all of them. Expanding `productCategories` in the tree shows all the fields added to the model: `categoryName`, `slug`, `description`, and `products`. ![API Playground with displayed tree](/images/docs/getting-started/api-playground-tree-displayed.png) Queries can be built by selecting fields in the tree or typed manually. All exercises below can be copied and pasted directly into the API Playground. ## GraphQL 1 This query fetches all product categories. The Plural API ID `productCategories` returns multiple entries. ```graphql query MyQuery { productCategories { categoryName description { text } slug } } ``` ```json { "data": { "productCategories": [ { "categoryName": "Clothes", "description": { "text": "You will find clothes here" }, "slug": "clothes" }, { "categoryName": "Shoes", "description": { "text": "You will find shoes here" }, "slug": "shoes" }, { "categoryName": "Sportswear", "description": { "text": "You will find sportswear here" }, "slug": "sportswear" }, { "categoryName": "Urban", "description": { "text": "You will find urban-style items here" }, "slug": "urban" }, { "categoryName": "New arrival", "description": { "text": "You will find our latest arrivals here" }, "slug": "new-arrival" }, { "categoryName": "Decor", "description": { "text": "You will find decor items here" }, "slug": "decor" }, { "categoryName": "Accessories", "description": { "text": "You will find accessories here" }, "slug": "accessories" } ] } } ``` The `description` field uses `text` as the output format because it was added as a Rich Text field in lesson 1.2. Rich Text fields require an explicit output format — `text`, `html`, `markdown`, or `raw`. Omitting the format returns an error. ## GraphQL 2 This query fetches all products in the **New arrival** category using a `where` filter on the category `slug`. ```graphql query MyQuery { productCategory(where: { slug: "new-arrival" }) { products { productName productSlug } } } ``` ```json { "data": { "productCategory": { "products": [ { "productName": "Colorful socks", "productSlug": "colorful-socks" }, { "productName": "Green Hoodie", "productSlug": "green-hoodie" }, { "productName": "Plaid shirt", "productSlug": "plaid-shirt" }, { "productName": "Black leather shoes", "productSlug": "black-leather-shoes" } ] } } } ``` The `products` field is queryable here because of the two-way many-to-many reference configured in lesson 2.1. The two-way direction is what makes it possible to navigate from a category to its products. Your response may differ depending on whether you completed the additional practice entries in lesson 6.1. Entries that were not created cannot be returned. ## GraphQL 3 **Try this yourself:** Find the `productName` and `productSlug` of all products in the `urban` category. ```graphql query MyQuery { productCategory(where: { slug: "urban" }) { products { productName productSlug } } } ``` ## References 1 This query fetches the related products connected to the plaid shirt entry. The `relatedProducts` field is a basic component field added to the `Product` model in lesson 4.2. The component contains a `title` field and a `products` reference field. ```graphql query MyQuery { product(where: { productSlug: "plaid-shirt" }) { relatedProducts { title products { productName productSlug } } } } ``` ```json { "data": { "product": { "relatedProducts": { "title": "Related Products", "products": [ { "productName": "Black leather shoes", "productSlug": "black-leather-shoes" }, { "productName": "Blue running shoes", "productSlug": "blue-running-shoes" }, { "productName": "Headband", "productSlug": "headband" }, { "productName": "Necklace", "productSlug": "necklace" } ] } } } } ``` The `title` field returns `Related Products` for every entry because it was configured as a read-only field with a fixed initial value in lesson 4.1. ## References 2 This query fetches all products in the `sportswear` category along with their product descriptions in HTML format. ```graphql query MyQuery { productCategory(where: { slug: "sportswear" }) { products { productName productSlug productDescription { html } } } } ``` ```json { "data": { "productCategory": { "products": [ { "productName": "Green Hoodie", "productSlug": "green-hoodie", "productDescription": { "html": "

This green hoodie combines comfort and style seamlessly, making it your go-to choice for a casual yet trendy look. Its soft fabric and versatile shade of green make it a wardrobe essential for all seasons.

" } }, { "productName": "Blue running shoes", "productSlug": "blue-running-shoes", "productDescription": { "html": "

These blue running shoes offer both form and function, providing exceptional support and style for your active pursuits. The breathable material and cushioned sole ensure a comfortable experience mile after mile.

" } } ] } } } ```
This response requires the additional practice entries from lesson 6.1. If you only created the core five products, Sportswear may return fewer results. ![Items in the sportswear category](/images/docs/getting-started/product-category-contents-example.png) ## References 3 **Try this yourself:** Query the `sellerInformation` reference on the homepage landing page entry. Find out the `businessName` and `businessDescription`. ```graphql query MyQuery { landingPage(where: { link: "/" }) { sellerInformation { businessName businessDescription { text } } } } ``` ## Components 1 This query fetches the `productVariant` component for the headband entry. Because `productType` is a modular component field, the query uses an inline fragment (`... on Accessory`) to specify which component type to query and which fields to return from it. ```graphql query MyQuery { product(where: { productSlug: "headband" }) { productName productDescription { html } productVariant { productType { ... on Accessory { color } } } } } ``` ```json { "data": { "product": { "productName": "Headband", "productDescription": { "html": "

This white cotton headband provides an elegant and sophisticated touch to your hairstyle. Perfect for completing your look with a blend of fashion and comfort.

" }, "productVariant": { "productType": { "color": "White" } } } } } ```
Inline fragments are required for modular component fields because the API needs to know which component type's fields to return. You can include multiple inline fragments in the same query to handle all possible component types simultaneously. For example, `... on Clothing { size color }` alongside `... on Accessory { color }`. ## Components 2 This query fetches the `relatedProducts` component for the blue running shoes entry, returning product descriptions in HTML format. ```graphql query MyQuery { product(where: { productSlug: "blue-running-shoes" }) { productName productDescription { html } relatedProducts { products { productDescription { html } } } } } ``` ```json { "data": { "product": { "productName": "Blue running shoes", "productDescription": { "html": "

These blue running shoes offer both form and function, providing exceptional support and style for your active pursuits. The breathable material and cushioned sole ensure a comfortable experience mile after mile.

" }, "relatedProducts": { "products": [ { "productDescription": { "html": "

These black leather shoes seamlessly blend sophistication with durability, making them a versatile choice for both formal occasions and everyday wear. The sleek design and high-quality leather ensure a timeless appeal that complements any outfit.

" } }, { "productDescription": { "html": "

This white necklace is crafted from parts of seashells, evoking a relaxed, beachy vibe. Perfect for those looking to add a touch of the sea to their daily style.

" } }, { "productDescription": { "html": "

This white cotton headband provides an elegant and sophisticated touch to your hairstyle. Perfect for completing your look with a blend of fashion and comfort.

" } }, { "productDescription": { "html": "

This plaid shirt exudes a timeless charm, perfect for a casual day out or a relaxed evening gathering. The classic checkered pattern and comfortable fit make it a wardrobe staple for effortless style.

" } } ] } } } } ```
## Components 3 This query filters all products by a value inside the `productVariant` modular component, specifically all Accessory type products where the color is White. ```graphql query MyQuery { products( where: { productVariant: { productType: { Accessory: { color: White } } } } ) { productName productSlug productDescription { html } } } ``` ```json { "data": { "products": [ { "productName": "Necklace", "productSlug": "necklace", "productDescription": { "html": "

This white necklace is crafted from parts of seashells, evoking a relaxed, beachy vibe. Perfect for those looking to add a touch of the sea to their daily style.

" } }, { "productName": "Headband", "productSlug": "headband", "productDescription": { "html": "

This white cotton headband provides an elegant and sophisticated touch to your hairstyle. Perfect for completing your look with a blend of fashion and comfort.

" } } ] } } ```
Filtering on a modular component field uses the component type name (`Accessory`) as a nested key in the `where` clause. The same pattern applies to `Clothing`, `Shoes`, and `Decor`. ## Components 4 **Try this yourself:** Query the `stripes` modular component inside the homepage landing page entry. Find the `productName` and `productSlug` of all products added to the Product Grid section. ```graphql query MyQuery { landingPage(where: { link: "/" }) { stripes { ... on ProductGrid { headline products { productName productSlug } } } } } ``` ## Remote Fields 1 This query fetches all products and their reviews from the remote field configured in lesson 5.2. The `reviews` field lives on the Product model and returns data from the HyDemoAPI remote source scoped to each product's slug. ```graphql query MyQuery { products { reviews { data { name comment rating } } } } ``` ```json { "data": { "products": [ { "reviews": { "data": [ { "name": "Sock Lover", "comment": "These socks are great. They are very colorful and they keep my feet warm.", "rating": 4.5 } ] } }, { "reviews": { "data": [ { "name": "Green Fan", "comment": "This is the absolute best hoodie I've ever had", "rating": 5 } ] } }, { "reviews": { "data": [ { "name": "Runner in Rochester", "comment": "These shoes are great. I've run 100 miles in them and they are still in great shape.", "rating": 5 } ] } }, { "reviews": { "data": [] } }, { "reviews": { "data": [ { "name": "Oregon Person", "comment": "After wearing this shirt for 2 days, I realized it was made of cotton. I am allergic to cotton. I am very disappointed.", "rating": 1.5 }, { "name": "Another Person", "comment": "This shirt is okay. It lost a button, but I sewed it back on.", "rating": 3.5 }, { "name": "Third Person", "comment": "I love this shirt. I wear it all the time.", "rating": 4.5 } ] } }, { "reviews": { "data": [ { "name": "First Person", "comment": "These shoes are both the most comfortable and the most stylish I have ever owned.", "rating": 4.5 }, { "name": "Second Person", "comment": "These would be better if they were red.", "rating": 2.5 }, { "name": "Third Person", "comment": "I've worn these for 3 years and they are still in great shape.", "rating": 4.5 } ] } }, { "reviews": { "data": [] } }, { "reviews": { "data": [] } }, { "reviews": { "data": [] } }, { "reviews": { "data": [] } } ] } } ``` Some products return an empty `data` array. This is expected because the HyDemoAPI only contains reviews for products whose slugs match entries in its dataset. Products with no matching reviews return an empty array, not an error. ## Remote Fields 2 This query fetches reviews scoped to a single product, the plaid shirt, using a `where` filter on `productSlug`. The remote field path uses `{{doc.productSlug}}` as the argument, so only reviews for that specific slug are returned. ```graphql query MyQuery { product(where: { productSlug: "plaid-shirt" }) { reviews { data { id name product comment rating } } } } ``` ```json { "data": { "product": { "reviews": { "data": [ { "id": 3, "name": "Oregon Person", "product": "plaid-shirt", "comment": "After wearing this shirt for 2 days, I realized it was made of cotton. I am allergic to cotton.", "rating": 1.5 }, { "id": 6, "name": "Another Person", "product": "plaid-shirt", "comment": "This shirt is okay. It lost a button, but I sewed it back on.", "rating": 3.5 }, { "id": 7, "name": "Third Person", "product": "plaid-shirt", "comment": "I love this shirt. I wear it all the time.", "rating": 4.5 } ] } } } } ``` ## Remote Fields 3 **Try this yourself:** Query the `reviews` remote field inside the **Product** model for the `blue-running-shoes` entry. Return the `rating` and `comment` for each review. ```graphql query MyQuery { product(where: { productSlug: "blue-running-shoes" }) { reviews { data { rating comment } } } } ``` ## Top-level Remote Fields 1 This query fetches landing page data and review data in a single API call. The `landingPage` and `reviews` fields are at the same level in the query because `reviews` is a top-level remote field on the Query model. It is not nested inside any content model. ```graphql query MyQuery { landingPage(where: { link: "/" }) { landingPageTitle link sellerInformation { businessName slug businessDescription { text } } } reviews(productSlug: "black-leather-shoes") { data { id name product comment rating } } } ``` ```json { "data": { "landingPage": { "landingPageTitle": "Home", "link": "/", "sellerInformation": { "businessName": "My business", "slug": "my-business", "businessDescription": { "text": "This is a description of my business" } } }, "reviews": { "data": [ { "id": 1, "name": "First Person", "product": "black-leather-shoes", "comment": "These shoes are both the most comfortable and the most stylish I have ever owned.", "rating": 4.5 }, { "id": 4, "name": "Second Person", "product": "black-leather-shoes", "comment": "These would be better if they were red.", "rating": 2.5 }, { "id": 5, "name": "Third Person", "product": "black-leather-shoes", "comment": "I've worn these for 3 years and they are still in great shape.", "rating": 4.5 } ] } } } ``` A single API call returned both Hygraph content and external review data with no middleware. This is Content Federation working as configured in lessons 5.1 through 5.3. ## Top-level Remote Fields 2 **Try this yourself:** Query the `reviews` top-level remote field for `productSlug: "plaid-shirt"`. Return only the `product` and `rating` fields. ```graphql query MyQuery { reviews(productSlug: "plaid-shirt") { data { product rating } } } ``` ## What's next Or, go to the [Tutorial overview](/docs/getting-started/tutorial/tutorial-overview) for the full lesson list. --- # Manage billing and plans Source: https://hygraph.com/docs/getting-started/update-billing **Project Owners** can manage project billing and plans by going to **Project Settings > General > Billing**. ## Manage billing ![Billing details](/images/docs/user-guides/update-billing.png) This screen displays information about your current plan, usage, and billing details. To view billing details related to your account, click **Billing details** in the top-right corner of the screen: ![Account - Billing details](/images/docs/user-guides/billing-details-account.png) The account billing details screen includes information about your subscription, invoices, account balance, and stored billing information. ## Manage plan To manage your plan, click **Manage plan** at the top-right of the screen. This view shows a side-by-side comparison of all available plans. ![Manage plan](/images/docs/user-guides/billing-manage-plan.png) You can compare plans on a yearly or monthly basis, and manage your upgrade directly from here. If you see that some plans are disabled for selection, this is because your project goes over the limits defined by those plans. ![Disabled plans in the Manage plan screen](/images/docs/user-guides/billing-over-limits.png) ## Commercial limits The limits for **API calls** and **Content entries** are not updated in real time. These values are refreshed once daily by a scheduled job. Any actions that affect these limits are reflected the following day after the job has run. The **API calls** and **Asset traffic** limits are aggregated across all environments within a project. All other limits are not shared between environments and apply only to the `master` environment. ### Hobby plan | PROJECT CAPABILITIES | LIMITS | | --------------------------------- | ------- | | Content entries | 1000 | | API calls | 500,000 | | AI tokens | 100,000 | | Asset traffic (GB) | 100 | | Seats | 3 | | Asset upload size (MB) | 50 | | Models | 20 | | Components | 10 | | Locales | 2 | | Remote Sources | - | | Content stages | 2 | | Version retention period | - | | Environments | 1 | | Webhooks | 5 | | API tokens | 3 | | PLATFORM CAPABILITIES | LIMITS | | --------------------------------------- | ------ | | Standard roles | 2 | | Custom roles & permissions | - | | Rate limits (uncached requests / RPS) | 5 | ### Growth plan | PROJECT CAPABILITIES | LIMITS | | --------------------------------- | --------- | | Content entries | 10,000 | | API calls | 1,000,000 | | AI tokens | 300,000 | | Asset traffic (GB) | 500 | | Seats | 10 | | Asset upload size (MB) | 200 | | Models | 40 | | Components | 20 | | Locales | 3 | | Remote Sources | 1 | | Content stages | 2 | | Version retention period | 14 days | | Environments | 2 | | Webhooks | 10 | | API tokens | 5 | | PLATFORM CAPABILITIES | LIMITS | | --------------------------------------- | ------ | | Standard roles | 4 | | Custom roles & permissions | - | | Rate limits (uncached requests / RPS) | 25 | ### 30-day trial | PROJECT CAPABILITIES | LIMITS | | --------------------------------- | --------- | | Content entries | 40,000 | | API calls | 5,000,000 | | AI tokens | 100,000 | | Asset traffic (GB) | 2,500 | | Seats | 40 | | Asset upload size (MB) | 1000 | | Models | 150 | | Components | 100 | | Locales | 15 | | Remote Sources | 6 | | Content stages | 3 | | Version retention period | 30 days | | Environments | 6 | | Webhooks | 25 | | API tokens | 10 | | PLATFORM CAPABILITIES | LIMITS | | --------------------------------------- | ------ | | Standard roles | 4 | | Custom roles & permissions | 5 | | Rate limits (uncached requests / RPS) | 50 | ### Enterprise plan | PROJECT CAPABILITIES | LIMITS | | --------------------------------- | -------------- | | Content entries | 1,000,000+ | | API calls | 50,000,000+ | | AI tokens | 2,000,000+ | | Asset traffic (GB) | 25,000+ | | Seats | 200 | | Asset upload size (MB) | Custom | | Models | up to 500 | | Components | up to 150 | | Locales | up to 80 | | Remote Sources | up to 10 | | Content stages | up to 5 | | Version retention period | up to 365 days | | Environments | Up to 10 | | Webhooks | Up to 100 | | API tokens | Up to 30 | | PLATFORM CAPABILITIES | LIMITS | | --------------------------------------- | --------- | | Standard roles | 4 | | Custom roles & permissions | Up to 30 | | Rate limits (uncached requests / RPS) | Up to 500 | --- # Content federation Source: https://hygraph.com/docs/core-concepts/content-federation Content federation lets you combine data from multiple systems and expose it through a single GraphQL API endpoint without migrating the content itself. Instead of moving product data from a commerce platform, inventory from a warehouse system, or reviews from a third-party service into Hygraph, you connect those sources directly and query everything from one place. ![Content Federation](/images/docs/getting-started/fundamentals/content-federation01.png) ## How content management has evolved The relationship between content management and content delivery has changed significantly over time. Traditional CMSs created a **one-to-one** relationship. Content was managed and presented by the same system, tightly coupled together. The headless CMS decoupled content management from the presentation layer, enabling a **one-to-many** relationship. Content is managed in one place and served via API to websites, apps, smart TVs, and more. Today, organizations need to go further. Content no longer lives in a single system. It is spread across legacy CMSs, PIMs, DAMs, SaaS tools, commerce platforms, and more. This creates the need for a **many-to-many** relationship, where content from multiple sources can be unified and delivered to multiple frontends. That is what content federation enables. ![What is Content Federation?](/images/docs/getting-started/fundamentals/content-federation02.png) ## What is content federation? Content federation is the process of sourcing data from multiple systems and making it available through a single GraphQL API, without duplicating or migrating the underlying content. In Hygraph, this is powered by **remote sources**. Remote sources allow you to connect external APIs, whether GraphQL or REST, directly to your Hygraph project. Once connected, content editors can reference data from those sources alongside native Hygraph content, and developers can fetch everything in a single API call. The use cases below show what content federation looks like in practice. ## Use cases ### E-commerce An e-commerce team typically manages product descriptions, editorial content, and campaign pages in Hygraph, while product pricing, stock levels, images, and categories live in a commerce platform such as Shopify, BigCommerce, or commercetools. Without content federation, keeping these in sync requires either migrating data into Hygraph or building custom middleware to stitch them together on the frontend. With content federation, editors reference products directly from the commerce platform using a product picker or SKU. When a page is rendered, Hygraph fetches the latest pricing and inventory from the commerce system automatically. Developers make one API call and get everything back in a single response. Pricing changes, stock updates, and product launches are reflected immediately, with no manual updates required in Hygraph. ### Travel A travel platform aggregates content from multiple providers, such as flight schedules from an airline API, hotel availability from a booking platform, and car rental options from a separate fleet management system. Managing this in a traditional CMS would require syncing data from all three sources on a schedule, which introduces lag and risks serving outdated availability or pricing to users. With content federation, Hygraph connects directly to each provider's API. Editorial content, such as destination guides, travel tips, and curated itineraries, lives natively in Hygraph, while live availability and pricing are pulled from the provider APIs at query time. Customers see accurate, real-time information without the content team having to manage data imports or the development team having to maintain a separate aggregation layer. ### Stock and inventory management A manufacturer or retailer operating across multiple warehouses needs to surface real-time inventory data on their website or internal tools. Stock levels change constantly and vary by location, making it impractical to maintain a copy of that data in the CMS. With content federation, Hygraph connects to the warehouse management system or ERP and pulls live inventory data alongside product content. Editors manage product descriptions, specifications, and marketing copy in Hygraph, while stock levels, lead times, and availability by location come directly from the source system. The result is a product page that always reflects actual inventory, with no synchronization scripts and no stale data. ## Benefits **Eliminates content silos** Content federation sources data directly from existing systems through a single API. Teams no longer need to migrate data from legacy platforms, and the frontend always receives the most current version. **Supports live data** For data that changes frequently, such as pricing, inventory, availability, and weather, content federation ensures the frontend always reflects the latest state. There is no risk of stale data from manual updates or scheduled syncs. **Preserves data integrity** With a single source of truth per data type, content federation removes the duplication that leads to inconsistencies. Data is not copied across systems. Instead, it is referenced programmatically from its origin. **Reuses existing content** Content that already exists in other systems can be brought into Hygraph without transformation or re-entry. Teams choose the best tool for each job and federate the results rather than consolidating everything into one platform. **Gives editors more autonomy** Because data flows through Hygraph rather than being stitched together on the frontend, editors can control what gets displayed on the final pages without requiring developer involvement for every change. **Connects third-party APIs** Hygraph can federate content from any API, regardless of whether it is GraphQL or REST, and expose it through a unified GraphQL endpoint. This includes public databases, review platforms, travel aggregators, stock management systems, and more. ## What's next To start using content federation, configure a remote source in your Hygraph project. - [Remote sources](/docs/developer-guides/remote-data/remote-sources): Full configuration instructions for connecting external APIs to your project. - Building a restaurant blog with Hygraph and the Yelp API: A hands-on tutorial that walks through setting up a remote source in a real project. --- # Best practices Source: https://hygraph.com/docs/core-concepts/content-modeling/content-modeling-best-practices A well-designed schema is one your editors, developers, and business stakeholders can all work with confidently. These best practices cover the decisions that have the most impact on the quality and longevity of your schema. ## Plan before you build The most common cause of schema problems is starting in the schema editor too early. Before adding a single model, you should have: - A clear picture of your business domain and the content it produces - A list of object domains and their attributes, agreed on as a team - A rough domain model showing how those objects relate to each other Building without this foundation leads to models that reflect how content is currently stored rather than how it should be structured — and restructuring later is expensive once editors have created content against the schema. For a guided walkthrough, see [Plan your content model](/docs/core-concepts/content-modeling/plan-your-content-model). ## Model content semantically Model what a piece of content is, not how it will look on a specific frontend. A model named `HeroBanner` with fields like `backgroundGradient` and `ctaButtonColor` is tightly coupled to a single frontend layout. A model named `FeaturedArticle` with fields like `headline`, `summary`, and `coverImage` describes content. The second approach works across any website, mobile app, or third-party integration, without requiring schema changes. This matters most if you plan to deliver content to more than one channel. Even if you are starting with a single channel, semantic models are easier to extend and maintain over time. ## Keep models focused Each model should represent one clear concept. If you find yourself adding a field that does not belong to the core concept a model represents, that is a signal to either create a new model or use a component. Models that try to serve too many purposes create confusion for editors, produce bloated API responses, and make the schema harder to reason about. A `LandingPage` model with 40 fields is almost always a sign that several concepts have been collapsed into one. ## Use components for repeated field groups If the same group of fields appears on more than one model, make it a component. Examples include: - SEO metadata (`metaTitle`, `metaDescription`, `canonicalUrl`) - Address fields (`street`, `city`, `country`, `postalCode`) - Call-to-action blocks (`headline`, `bodyText`, `buttonLabel`, `buttonUrl`) Defining these once as components means you update them in one place. If you later need to add an `ogImage` field to your SEO component, it propagates to every model that uses it automatically. Use modular components when editors need to compose content from a variable set of building blocks. For example, a page that can contain any combination of hero sections, feature grids, and testimonials. ## Define relationships explicitly Avoid duplicating data across models. If two models share a concept, use a reference field rather than adding author fields to both models. For example, both a `Product` and a `BlogPost` refer to an `Author`. Explicit relationships have several benefits: - Modifying an author's name or photo updates it wherever the author is referenced - You can query related content in a single API call - The schema accurately reflects how your business domain is structured When setting up a two-way reference, think carefully about whether the relationship is one-to-one, one-to-many, or many-to-many, before creating the field. ## Establish naming conventions early Inconsistent naming creates confusion for editors and makes the API harder to work with. Agree on a naming convention before building and apply it consistently. A few patterns worth deciding on upfront: - **Field naming**: Stick to camelCase, which is the Hygraph default for API IDs. - **Boolean fields**: Prefix with `is` or `has` to make the meaning clear (`isPublished`, `hasFeaturedImage`). - **Reference fields**: Name them after the model they point to, in singular or plural depending on cardinality (`author` for one, `authors` for many). - **Model names**: Use singular nouns (`Article`, not `Articles`). Naming conventions are especially important in larger projects where multiple developers and editors work on the schema over time. ## Design for editorial usability A schema that is technically correct but difficult to use will slow down content creation and lead to errors. When adding fields to a model, consider: - **Field order**: Put the most important fields at the top of the form. Editors fill forms top to bottom. - **Required fields**: Only mark a field as required if an entry genuinely cannot be published without it. Over-requiring fields creates friction. - **Field descriptions**: Use the description option on fields to explain what is expected, especially for fields where the purpose is not obvious from the name. - **Enumerations over free text**: Where a field has a fixed set of valid values, use an enumeration rather than a text field. This prevents inconsistent data entry and reduces editorial errors. ## Plan for growth A schema that works for your current content volume may not work at scale. A few things to consider: - **Avoid deeply nested structures**: Deep nesting creates complex queries and can affect API performance. If you find yourself nesting components inside components inside components, reconsider the structure. - **Use environments to test schema changes**: Before making structural changes to a live schema, test them in a separate environment to verify the impact on queries and editorial workflow. - **Revisit the schema periodically**: As the business changes, the schema should change with it. A periodic review against current business goals catches misalignment before it becomes a problem. ## Dos and don'ts Use this as a quick-reference checklist when reviewing a schema. ### Dos | Guideline | Description | |-----------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------| | **Start with research** | Begin with desk research and stakeholder interviews to define business goals and user needs before opening the schema editor. | | **Deconstruct the domain** | Break your business down into key object domains and their relationships before deciding what goes into the schema. | | **Work with a cross-functional team** | Involve people from multiple departments. Efficient modeling requires editorial, technical, and strategic perspectives. | | **Prioritize flexibility** | Design a schema that can accommodate new models and structural changes as the business evolves. | | **Keep structure separate from design** | Base your content model on relationships and meaning, not on how content will look visually on the frontend. | | **Keep it as simple as possible** | A simpler model is easier to maintain and easier for editors to work with. Model for meaning, not for layout. | | **Define clear relationships** | Use reference fields to connect related models rather than duplicating data. Clear relationships make content reusable and queries predictable. | | **Use components** | Create reusable groups of fields with components to reduce redundancy across models. | | **Establish taxonomy early** | Define a classification system at the start of the project to improve content discoverability and ensure consistent categorization. | ### Don'ts | Don't | Description | |-------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------| | **Don't model alone** | Content modeling affects everyone who works with the CMS. It should always involve a cross-functional team. | | **Don't overcomplicate the model** | Unnecessary complexity slows development, makes content management harder, and can create performance problems. | | **Don't overlook relationships** | Failing to define relationships between models leads to duplicated data and missed opportunities for content reuse. | | **Don't ignore scalability** | Make sure your schema can grow. A model that works perfectly today can become a bottleneck as the business changes. | | **Don't assume editor needs** | Always talk to your editors. Never guess at what their workflow requires — ask them directly. | | **Don't use inconsistent taxonomy** | Inconsistent categorization creates confusion and makes content hard to manage. Agree on terms as a team before you start. | | **Don't duplicate content unnecessarily** | Duplicating content increases maintenance overhead and introduces the risk of inconsistencies. Use references and components instead. | | **Don't isolate the model from business goals** | Align your schema with what the business is trying to achieve, and revisit that alignment periodically as goals change over time. | ## What's next - [Plan your content model](/docs/core-concepts/content-modeling/plan-your-content-model): A practical guide to research, stakeholder involvement, and schema design. - [Content modeling walkthrough](/docs/core-concepts/content-modeling/content-modeling-walkthrough): See these principles applied in a complete schema walkthrough. --- # Content modeling in Hygraph Source: https://hygraph.com/docs/core-concepts/content-modeling/content-modeling-in-hygraph Once you have a clear picture of your content model conceptually, you need to know how to express it in Hygraph. This page covers the schema building blocks available to you: what each one is, what it does, and when to use it. ![Content modeling with Hygraph](/images/docs/user-guides/content-modeling-with-hygraph.png) ## Models A model is the core building block of your schema. It defines the structure of a single kind of entry. `Article`, `Author`, `Product`, and `Category` are all examples of models. In Hygraph, every model you create automatically generates the corresponding GraphQL queries and mutations in your Content API. You do not write any API code manually; instead, the schema editor generates it as you build. Think of a model as analogous to a database table. The model defines the structure and individual content entries are the rows. ## Fields Fields define what data a model stores. Each field has a type that determines both the kind of data it accepts and how it appears in the content editor. A `Single line text` field renders as a text input; an `Asset picker` field renders as a file uploader; a `Boolean` field renders as a toggle. Hygraph provides the following scalar field types: | Type | Use for | |------------------|------------------------------------------------| | Single line text | Short strings: titles, names, slugs | | Multi line text | Longer plain text without formatting | | Rich text | Formatted content with headings, links, embeds | | Markdown | Markdown-formatted content | | Integer | Whole numbers | | Float | Decimal numbers | | Boolean | True/false values | | Date | Date without time | | Date and time | Date with time | | Color | Hex color values | | Location | Geo coordinates | | JSON | Arbitrary JSON data | For the complete field type reference including configuration options, see [Field types](/docs/api-reference/schema/field-types). ### Field modifiers Every field supports a set of modifiers that change how it behaves: | Modifier | Description | |-----------|------------------------------------------------------------------------------| | Required | The field must have a value before an entry can be saved | | Unique | No two entries in the same model can share the same value for this field | | List | The field stores multiple values of the same type rather than a single value | | Localized | The field stores a separate value per locale | Modifiers can be combined. A localized, required `Single line text` field, for example, must have a value in each locale where you save that localization. When publishing, you can select which locales to include; the default locale is always required. For the full modifier reference, see [Field configuration](/docs/api-reference/schema/field-configuration). ## References References connect models to each other. A reference field on a `Product` model that points to a `Product Categories` model creates a relationship between the two. You can navigate from a product to its category and query both in a single API call. Hygraph supports two-way references, which means the relationship is visible from both sides. When you create a reference from `Product` to `Product Categories`, a reverse field is automatically available on `Product Categories` that lists all products associated with that category. References can be configured to allow one or many related entries, and they can be restricted to a single model or open to multiple model types (using a union reference). ![References](/images/docs/getting-started/content-modeling/hygraph-relations.png) ## Components A component is a reusable group of fields. Unlike a model, a component cannot exist as a standalone entry. It is always embedded within a model or another component. Components are useful when the same group of fields appears across multiple models. An `SEO` component containing a meta title, meta description, and canonical URL field, for example, can be added to every model that needs it. Update the component definition once and it updates everywhere. Hygraph supports two types of component fields: | Type | Description | |-------------------|---------------------------------------------------------------------------------------------| | Basic component | Embeds a single component type. Can allow one or multiple instances. | | Modular component | Allows editors to choose from a set of different component types when filling in the field. | Modular components are particularly powerful for page-building patterns, where editors can compose a page from a defined set of blocks, such as a hero, a feature list, a testimonial. No developer support is required to create a separate model for each page layout. ![Components](/images/docs/getting-started/content-modeling/composable.png) ## Enumerations An enumeration (enum) defines a fixed set of allowed values for a field. At the API level, GraphQL enums provide schema-level validation. A field typed to an enum can only contain one of the defined values. Enums work well for status values (`DRAFT`, `PUBLISHED`, `ARCHIVED`), sort orders (`NAME_ASC`, `PRICE_DESC`), or any other field where the set of valid values is known in advance and should be enforced. ## Taxonomies Taxonomies let you define hierarchical, centrally managed vocabularies that classify content across multiple models. Each taxonomy is a tree of nodes arranged in a parent-child hierarchy, supporting up to six levels of depth. For example: `Root → Category → Subcategory → Topic` Once defined, a taxonomy is applied to a model as a taxonomy field. Editors select a node from the hierarchy when creating or updating a content entry. At the API level, a taxonomy field exposes two values: - `value`: The taxonomy node assigned to the entry. - `path`: An array of the full hierarchy path up to the assigned node. This path-based structure enables advanced GraphQL filtering using operators like `descendants_of`, making taxonomies well suited to faceted navigation, personalized content feeds, and search filtering. ## System artifacts Hygraph automatically generates a set of models, fields, and capabilities that every project includes by default. ### Asset model The `Asset` model stores uploaded files: images, videos, audio files, and PDFs. Every project has one automatically. Hygraph includes a built-in image transformation API that lets you crop, resize, and reposition images at query time by passing transformation parameters in the URL or GraphQL query, rather than storing multiple variants. ### User model Every Hygraph project member is a `User`. The user model is managed automatically and is connected to every content entry and asset, so you can always identify who created, updated, or published a piece of content. User data is also available through the Content API, so you can query author names and avatars alongside content if your application needs it. ### System fields Hygraph adds the following fields to every model automatically. You do not need to define them. | Field | Type | Description | |--------------------|----------|--------------------------------------------| | `id` | String | A unique identifier for each entry | | `createdAt` | DateTime | When the entry was first created | | `updatedAt` | DateTime | When the entry was last modified | | `publishedAt` | DateTime | When the entry was last published | | `createdBy` | User | The user who created the entry | | `updatedBy` | User | The user who last modified the entry | | `publishedBy` | User | The user who last published the entry | | `documentInStages` | [model] | Query the current document in other stages | ## Modeling for content reuse If you plan to deliver content across multiple platforms, such as a website, a mobile app, a third-party integration, keep your models free of presentational information. A model named `Hero` with fields like `backgroundGradient` and `buttonAlignment` is tightly coupled to a specific frontend layout. A model named `FeaturedArticle` with fields like `headline`, `summary`, and `coverImage` describes content, not presentation. The second approach works across any platform, whereas the first only works for one. This principle is called **semantic modeling**: model what a piece of content *is*, not how it should *look*. Hygraph's API delivers the data; the frontend decides what to do with it. If you are building for a single channel and your editors need to work independently on page layouts, structuring models closer to your frontend component structure is a reasonable trade-off. Just be aware that it reduces content reusability. ## Access controls Once your schema is defined, you can configure fine-grained permissions for every model and field. Permissions control what content editors can see and edit in the UI, and what API tokens can read or write through the Content API. For example, you can create an editor role that can update `Article` entries but cannot publish them, or an API token scoped to read-only access on a subset of models. For the full permissions reference, see [Roles and permissions](/docs/getting-started/access-and-permissions/user-roles-and-permissions). ## What's next - [Plan your content model](/docs/core-concepts/content-modeling/plan-your-content-model): A practical guide to research, domain modeling, and schema design before you start building. - [Field types](/docs/api-reference/schema/field-types): Complete reference for all field types and their configuration options. - [Field configuration](/docs/api-reference/schema/field-configuration): Reference for field modifiers, validations, and appearance settings. - [Content modeling walkthrough](/docs/core-concepts/content-modeling/content-modeling-walkthrough): See all of these concepts applied in a complete schema walkthrough. --- # Content modeling overview Source: https://hygraph.com/docs/core-concepts/content-modeling/content-modeling-overview Before you create a model or add a single field in Hygraph, you need a content model. It determines how your content is structured, how pieces of content relate to each other, and how your team works with it every day. A well-designed content model makes your schema easy to extend, your API predictable, and your editorial workflow efficient. A poorly designed one creates bottlenecks that no amount of tooling can fix. This page covers the foundational questions you need to answer before opening the schema editor. ## What is content modeling? A content model is the blueprint for your content. It defines what types of content exist in your system, what each type contains, and how types relate to each other. In Hygraph, the content model lives in the schema editor. Each **model** represents a content type. For example, `Article`, `Author`, or `Product`. Each model contains **fields** that define the shape of an entry: a title, a publication date, a cover image, a reference to another model. The relationships between models that are defined through reference fields make a content model more than a collection of forms. ### What is structured content? Structured content is data organized into discrete, well-defined elements rather than one undifferentiated block of text. Instead of a single document, you have a title field, a summary field, a date field, an image field. Each field has a clear purpose and is independently retrievable. The practical consequence is reusability. A product name, price, and cover image defined once in a `Product` model can appear on a product detail page, a category listing, a homepage feature, and a search result, without any duplication. Content modeling is the step that formalizes this structure. Identify how the concepts in your business domain relate to each other and translate that into a schema. The schema then governs how content is created, stored, queried, and reused. ![Structured content](/images/docs/getting-started/content-modeling/hygraph-one-graphql-api.png) ## Why content modeling matters A content model sits at the intersection of your business goals, your users' needs, your editors' workflows, and your developers' requirements. A model that ignores any one of these groups will create friction for that group. Here is what poor content modeling looks like in practice: - **Performance problems**: Complex queries, deeply nested structures, and redundant data slow down your application and increase API response times. - **Duplicate content**: Without clear relationships, editors create the same content multiple times in different places. Updates become inconsistent, maintenance becomes expensive. - **Scalability limits**: Rigid, tightly coupled structures resist change. Adding a new content type or distribution channel becomes a much larger undertaking than it should be. - **Editor friction**: A CMS built on a poorly designed model is difficult to navigate. Forms don't match how editors think about content, and content quality drops as a result. - **Developer friction**: An API shaped by a bad model is hard to work with. Developers spend time compensating for schema decisions rather than building features. In these scenarios, the CMS is not the bottleneck, but the model is. ## When to start Before you write code, configure a frontend, or create a single entry, you need to think about content modeling. The reason is structural. Content modeling is a bottom-up process. You define the shape your data will take, how it will connect, and how it will grow. That definition then informs every decision downstream, such as API design, frontend architecture, editorial workflow, and integration strategy. Hygraph lets you adjust your schema at any point, and [environments](/docs/api-reference/basics/environments) make it safer to test structural changes without touching production data. But the later a change is introduced, the harder it becomes. Once you have live content, schema changes can require data migrations, which cost time and resources. A solid model upfront prevents that. If you are already mid-project and considering a large structural change, use [Hygraph environments](/docs/api-reference/basics/environments) to test the change in isolation before applying it to your production schema. ## Who should be involved Content modeling is a cross-functional activity. No single person has full visibility into the business goals, user needs, editorial workflows, and technical constraints that a good model has to balance. ![Who should be involved](/images/docs/getting-started/content-modeling/why-content-modeling-matters.png) - **Content creators**: The people who use the CMS every day. Their workflow determines what fields and structures need to exist. Involve them early and interview them directly rather than guessing at their needs. - **Designers**: Whether you start with the content model, wireframes, visual designs, or code, the team designing your project should be involved in thinking through the design of your content as well. - **Business stakeholders**: Whoever is accountable for business outcomes should weigh in on what content serves those outcomes. - **Developers**: Developers understand technical constraints such as performance, API shape, integration requirements. Their input prevents decisions that are editorially convenient but technically expensive. - **Content strategist**: If your team has one, this person should lead or facilitate the modeling process. They bridge the gap between business needs and content structure. The goal is to have a model that no single group had to compromise on because all groups helped shape it. ## Where to model your content Complete your initial research and domain modeling before opening the schema editor. Once you have a clear picture of your content types and relationships, build and iterate directly in Hygraph rather than maintaining a separate planning document that will diverge from the real schema. Working in the schema editor means you can see exactly what an editor will see when filling in a content form, based on the fields you have defined. You can test queries against your schema in real time to verify the API shape before building against it. You can use environments to experiment with structural changes safely. Running modeling sessions inside Hygraph with editors, developers, and stakeholders is one of the most effective ways to catch problems early and build a shared understanding of the schema before content creation begins. ## What's next - [Content modeling in Hygraph](/docs/core-concepts/content-modeling/content-modeling-in-hygraph): Learn how Hygraph's schema primitives (models, fields, components, enumerations) work before you build. - [Plan your content model](/docs/core-concepts/content-modeling/plan-your-content-model): A practical guide to research, stakeholder sessions, domain modeling, and schema design. - [Best practices](/docs/core-concepts/content-modeling/content-modeling-best-practices): Best practices for designing a scalable, maintainable, and editor-friendly content model. - [Content modeling walkthrough](/docs/core-concepts/content-modeling/content-modeling-walkthrough): A complete walkthrough of building a schema from scratch. If you are following the Getting Started tutorial, these concepts are applied directly in [Lesson 1.1: Design your content models](/docs/getting-started/tutorial/tutorial-design-content-models). --- # Content modeling walkthrough Source: https://hygraph.com/docs/core-concepts/content-modeling/content-modeling-walkthrough This walkthrough takes you through the full content modeling process using a bookshop website as an example. Starting with research and stakeholder interviews, you will build a domain model with your team and then translate it into a working Hygraph schema. Following this method produces a schema that is structured, scalable, and straightforward for both editors and developers to work with. ## Prerequisites - Read [Content modeling overview](/docs/core-concepts/content-modeling/content-modeling-overview) for the conceptual foundation. - Read [Content modeling in Hygraph](/docs/core-concepts/content-modeling/content-modeling-in-hygraph) to understand models, fields, components, and other schema primitives. - Read [Plan your content model](/docs/core-concepts/content-modeling/plan-your-content-model) for guidance on the research and domain modeling process. - Have a Hygraph project ready. If you don't have one yet, [create a project](/docs/getting-started/onboarding-overview) first. ## Step 1: Research and discovery Content modeling starts with research, not with the schema editor. For this example, imagine you have been asked to build a content model for a chain of bookshops. Start with **desk research** to understand how bookshop websites typically structure their content. This would include book details, genres, authors, reviews, and so on. Once you have a basic understanding, interview key stakeholders: - **Content creators** to understand how they enter book details, manage inventory, and create content day-to-day. - **Sales team** to learn what information supports the purchasing decision and how customers interact with the site. - **Developers** to understand technical requirements, preferred stack, and how content needs to be structured for performance. - **Marketing team** to identify what drives engagement and what promotional content needs to be supported. After your research and interviews, compile a list of **terms** that will form the foundation of your content model:
- Books - Authors - Genres - Reviews
- Price - ISBN - Availability - Promotions
### Map the relationships With your terms defined, think through how these concepts relate to each other: - Each **Book** is written by one or more **Authors**. - Each **Book** belongs to one or more **Genres**. - Each **Book** may have multiple **Reviews**. - Each **Book** has a single **ISBN**. - Each **Genre** may be associated with multiple **Books** and **Authors**. - Each **Book** has a **Price**. Next, separate **object domains** (things that will become models) from **attributes** (values that describe an object). `Price`, `ISBN`, and `Availability` are attributes of `Book`, not models in their own right. ## Step 2: Build the domain model Bring your cross-functional team together to build a **domain model**, which is a high-level picture of the entire business, its key objects, and the relationships between them. The domain model is broader than what you will put into Hygraph. It covers the full scope of the business, including operational areas that are not directly relevant to the website. Here is a domain model for the bookshop: | Object domain | Attributes | |-------------------------|--------------------------------------------------------------| | **Books** | Title, ISBN, price, availability, summary, cover image | | **Authors** | Name, biography, photo | | **Genres** | Name, description | | **Reviews** | User name, rating, review text | | **Promotions** | Discount name, description, amount, active period | | **Store locations** | Shop name, address, contact details, operating hours | | **Employee management** | Employee name, role, shift schedule, contact information | | **Suppliers** | Supplier name, contact information, order history, contracts | Here are the key relationships: | Objects | Relationship | |------------------------------------|--------------------------------------------------------------------------------------------------------------| | `Books` and `Authors` | A book is written by one or more authors. An author can write multiple books. | | `Books` and `Genres` | A book belongs to one or more genres. A genre can contain multiple books. | | `Books` and `Reviews` | A book can have multiple reviews. A user can review multiple books. | | `Books` and `Promotions` | A book can be associated with one or more active promotions. A promotion can apply to multiple books. | | `Books` and `Suppliers` | A supplier provides one or more books. A book may be sourced from multiple suppliers. | | `Store locations` and `Books` | A store location stocks multiple books. A book may be available in multiple locations. | | `Suppliers` and `Store locations` | A supplier provides stock to one or more locations. A store location receives stock from multiple suppliers. | | `Promotions` and `Store locations` | A promotion may apply to specific locations. A store location may have multiple active promotions. | The domain model covers the full business, but the Hygraph schema only needs the object domains that relate to content your users interact with. This includes books, authors, genres, and promotions. Store locations, employee management, and suppliers are essential for business operations but are not part of the customer-facing content model. ## Step 3: Build the schema With a domain model in hand, identify which parts belong in Hygraph. In Hygraph, object domains become **models** and attributes become **fields**. Here is the high-level schema: - **Book** model: Title, ISBN, Price, Summary, Availability, Book cover, Genres (reference), Author (reference), Promotion (component field) - **Author** model: Name, Biography, Photo, Books (reference), Genres (reference) - **Genre** model: Name, Description, Books (reverse reference), Authors (reverse reference) The sections below define each model in detail. ### Book model The `Book` model is the primary content type on the bookshop website. | Field | Type | Description | |----------------|---------------------|-----------------------------------------| | `title` | Single line text | The book's title | | `isbn` | Single line text | A unique identifier for the book | | `price` | Float | The book's price | | `summary` | Rich text | A description of the book | | `availability` | Boolean | Whether the book is currently in stock | | `bookCover` | Asset picker | The book's cover image | | `genres` | Reference (two-way) | Links to one or more `Genre` entries | | `author` | Reference (two-way) | Links to one or more `Author` entries | | `promotion` | Basic component | Adds a promotion component to the entry | #### Promotion component The promotion component is a reusable group of fields attached to the `Book` model. Because it is a component, the same definition can be reused on other models without duplicating the field setup. | Field | Type | Description | |---------------|------------------|----------------------------------------| | `name` | Single line text | The promotion's name | | `discount` | Enumeration | A predefined list of discount values | | `description` | Rich text | A description of the promotion | | `image` | Asset picker | An image associated with the promotion | A component is a predefined group of fields that can be reused across models without duplicating field definitions. Using a component here means promotion data is consistent wherever it appears, and changes to the component propagate automatically. For this simplified example, promotions are embedded per book rather than modeled as shared `Promotion` entries. In a production schema where the same promotion applies to many books, use a `Promotion` model with a two-way many-to-many reference to `Book` instead. ### Author model The `Author` model stores information about book authors. | Field | Type | Description | |-------------|---------------------|--------------------------------------| | `name` | Single line text | The author's full name | | `biography` | Rich text | A biographical description | | `photo` | Asset picker | A profile image | | `books` | Reference (two-way) | Links to one or more `Book` entries | | `genres` | Reference (two-way) | Links to one or more `Genre` entries | ### Genre model The `Genre` model stores genre definitions and connects to both books and authors through reference fields. | Field | Type | Description | |---------------|---------------------|-----------------------------------------------------| | `name` | Single line text | The genre name. For example, Fiction or Non-Fiction | | `description` | Rich text | Additional information about the genre | | `books` | Reference (reverse) | Reverse field of the `genres` reference on `Book` | | `authors` | Reference (reverse) | Reverse field of the `genres` reference on `Author` | ## What's next You now have a schema that reflects a structured, scalable approach to content modeling. Editors can create entries efficiently, and developers can query the Content API with confidence because the relationships are explicit and field types are predictable. As your project grows, revisit the schema periodically to check that it still aligns with your business goals. Use [environments](/docs/api-reference/basics/environments) to test structural changes safely before applying them to production. | Resource | Description | |-----------------------------------------------------------------------|------------------------------------------------------------------------------| | [Getting Started tutorial](/docs/getting-started/onboarding-overview) | Build a project from scratch, from schema design to frontend implementation. | | [Hygraph recipes](/docs/recipes) | Explore common schema patterns and field configurations. | | [Implementation guides](/docs/implementations) | Step-by-step guides for connecting your Hygraph project to a frontend. | | [Integration guides](/docs/integrations) | Find out how third-party apps integrate with Hygraph to extend your project. | --- # Plan your content model Source: https://hygraph.com/docs/core-concepts/content-modeling/plan-your-content-model A well-designed schema starts well before you open the schema editor. The decisions you make during planning, such as understanding your domain, aligning your team, and mapping relationships between concepts, determine whether your schema will serve you for years or constrain you within months. This guide walks through each stage of the process. ## Deconstruct your domain Start by understanding the subject area your content will cover. Identify the key concepts in your domain and consider how they connect to each other. Your research can begin online, but it should include conversations with subject matter experts (SMEs) and real users. The output of this process is a list of terms and definitions that you will use when building your schema. These concepts need to be clear and agreed upon before modeling begins. Ambiguous terminology leads to ambiguous models. ![References](/images/docs/getting-started/content-modeling/hygraph-references.png) As you build your list, distinguish between **object domains** and **attributes**: - An **object domain** is a thing that exists independently. For example, `Author` or `Book`. It will likely become a model in your schema. - An **attribute** is a characteristic of an object. For example, `name` is an attribute of `Author`, not an object in its own right. If a concept can be answered with a specific value, it is probably an attribute. It will likely become a field in your model. ## Involve your stakeholders Content modeling requires cross-functional input. Research gives you a starting point, but the perspectives of people across your organization, such as editors, developers, researchers, and business leaders, are what make a model genuinely usable by everyone who works with it. ### Who to involve - **UX researchers and BI professionals**: UX researchers have detailed knowledge of user behavior and needs. BI professionals can identify business performance trends the model should support. Both help align the schema with real-world usage. - **Individual contributors**: The people who work inside the CMS every day know what works and what doesn't. Editors know the pain points of content creation. Developers know the technical constraints. Sales and support teams know what customers actually need from the product. - **External experts**: Depending on your subject domain, an outside expert can fill gaps that internal teams cannot cover. ### Why it matters Collaborating across roles reduces silos, surfaces blind spots, and produces a model that serves everyone who works with it. The people who will use the schema every day should help design it. ### How to run discovery sessions Prepare before each session. Write out your questions in advance and decide how you will capture the information. Recording sessions, with the interviewee's consent, is more reliable than notes alone. One effective technique is **card sorting**. Write key concepts on cards and ask the interviewee to organize them into categories while explaining their reasoning. Bring blank cards so interviewees can add concepts you have not thought of yet. After your sessions, compile what you gathered into three things: 1. A list of terms and their definitions. 2. A map of how those terms relate to each other. 3. A clear separation of object domains from attributes. You will use all three directly when building your domain model. ## Build a domain model Bring your cross-functional team together to build a **domain model**, which is a high-level map of your business, its key objects, and how those objects relate to each other. The domain model covers the full scope of the business, not just what will be published through the CMS. For a bookshop, that includes inventory, orders, promotions, staff management, and suppliers, not just books and authors. You need this full picture before deciding what belongs in the schema. A practical way to run this session is with sticky notes or a shared whiteboard tool. Arrange your object domains spatially and draw connections between them. Since you have done your research beforehand, bring a first-draft sketch rather than starting from a blank canvas. This gives the team something to react to and refine rather than building from scratch. The result should be a shared, agreed-upon picture of how your business operates. This becomes the reference for the next step, where you decide which parts of the domain model belong in your Hygraph schema. ## Build your schema You can use Hygraph's schema editor to draft and iterate on your schema as you go. This lets you see what a content entry form will look like for editors at every step of the way. Your schema is the portion of the domain model you bring into Hygraph.You need to bring in only those objects that represent content your editors will create and your users will interact with. Not every object from the domain model belongs in the schema. Look at your domain model and select the object domains relevant to published content. These become **models** in Hygraph. The attributes of each object become **fields** on those models. Relationships between objects become **reference fields**. For example, if you have an `Author` model, then `name`, `bio`, and `picture` would be fields on it. A relationship between `Author` and `Book` would be a two-way reference field, meaning you can navigate from a book to its author and from an author to their books. ![Hygraph project schema](/images/docs/getting-started/content-modeling/hygraph-schema.png) The result is a schema containing a set of models connected by references, where each model contains the fields that represent its attributes. ### Content templates and dynamic page composition ![Content templates & dynamic page composition](/images/docs/getting-started/content-modeling/hygraph-relations-attributes.png) Think of each model as a template. When you defined your models and fields during planning, you were deciding what those templates would contain. Now you build them out. Hygraph's **components** are particularly useful here. A component is a predefined group of fields that can be reused across different models without duplicating field definitions. Component fields can be: - **Basic**: Embeds a single component type, allowing one or multiple instances. - **Modular**: Allows editors to choose from a set of different component types when filling in the field. Using a modular component field, you can create a model that behaves differently depending on what the editor selects. A `Book` model could have a modular component field for banners, where editors choose between a promotional banner and a new arrivals slider, or add no banner at all. A single model can have multiple possible content configurations. ### Classification using references A common pattern for organizing and categorizing content in Hygraph is to create a dedicated model for each category type, create entries for each category value, and connect that model to the models that need categorizing using a two-way reference field. For example, a `Genre` model with a `name` field and a `description` field can be connected to a `Book` model via a two-way reference. Editors assign one or more genres to each book entry. At the API level, you can query all books in a given genre, or query a book and retrieve its genres in the same request — because the relationship is defined in the schema. This approach works well when category entries need their own rich content, such as descriptions, images, or related metadata. The same `Genre` model can be referenced by multiple other models, keeping classifications consistent without duplication. If you need a hierarchical, centrally governed classification system that is shared across many models, consider using Hygraph's dedicated [Taxonomies](/docs/developer-guides/schema/taxonomies) feature instead. Taxonomies are an enterprise feature and support up to six levels of hierarchy with advanced API filtering capabilities. ## Terminology reference Content modeling literature uses different terminology than Hygraph. This table maps the common terms to their Hygraph equivalents. | Content modeling term | Hygraph term | |-----------------------|-----------------------| | Content model | Schema | | Object domain | Model | | Content type | Model | | Attribute | Field | | Property | Field | | Relationships | References, relations | | Content instance | Entry | ## What's next - [Content modeling in Hygraph](/docs/core-concepts/content-modeling/content-modeling-in-hygraph): Reference for all schema building blocks, such as models, fields, components, enumerations, and system artifacts. - [Best practices](/docs/core-concepts/content-modeling/content-modeling-best-practices): Best practices for designing a scalable, maintainable, and editor-friendly content model. - [Content modeling walkthrough](/docs/core-concepts/content-modeling/content-modeling-walkthrough): A complete walkthrough of building a schema from scratch. --- # Glossary Source: https://hygraph.com/docs/core-concepts/glossary ## Basic concepts | Concept | Description | |------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | API Playground | The [API Playground](/docs/api-reference/basics/api-playground), located on the left sidebar of the Hygraph app, allows you to experiment with querying and mutating data. | | Assets | The [Asset](/docs/api-reference/assets/assets-overview) model is included in every project by default. It is commonly used for images, but can also support any file type. Assets can be fetched, referenced, transformed, uploaded, updated, published, localized, deleted, and embedded. | | Component | A [component](/docs/developer-guides/schema/components) is a predefined set of fields that can be reused across models and content entries. You can think of a component as a flexible, reusable template where you define the fields once, and then fill them with different content every time you use it in a content entry. | | Content modeling | The [content model](/docs/core-concepts/content-modeling/content-modeling-in-hygraph) defines the schema of your content. It describes what kind of content you want to store, how it relates to other content, and how it can be edited within Hygraph. | | Content views | Hygraph features default and custom [content views](/docs/developer-guides/content/content-views). Default content views list all content entries created based on the respective models. Custom content views are filtered for specific content entries. | | Enumeration | An [enumeration](/docs/api-reference/schema/enumerations) (or enum for short) can be used to group values within a type. Enums are useful for filtering and defining certain content entries in your project. | | Model | Models define the structure of your content. You build your schema by creating models and adding fields to them. Learn more in the [quickstart guide](/docs/getting-started/quickstart#create-a-model). | | Mutations | Operations that allow you to insert new data or modify existing data on the server side. See [mutations](/docs/api-reference/content-api/mutations) for the full reference. | | Preview URLs | [Preview URLs](/docs/developer-guides/schema/live-preview#define-a-preview-url-template) are automatically generated links that allow you to preview content in your frontend before it is published. Configuring this feature adds a button to the content editor's right sidebar so editors can quickly access content previews. | | Relations | References, often referred to as [relations](/docs/api-reference/schema/field-types#reference), allow you to connect one or more models together, and even reference multiple models as a single field type using GraphQL Union Types. | | Remote sources | [Remote sources](/docs/developer-guides/remote-data/remote-sources) are Hygraph's content federation feature. They allow you to add content from other systems and sources to the Hygraph API without migrating the content itself. | | Schema editor | The schema editor is where you add fields, create relationships between models, and define the structure of your content. See the [Studio walkthrough](/docs/getting-started/studio-walkthrough) to learn more. | | System fields | All default and custom models come with managed [system fields](/docs/api-reference/schema/system-fields). These fields reflect certain states of your content, such as when it was last updated or published. | | Taxonomy | A [taxonomy](/docs/developer-guides/schema/taxonomies) is a group of terms arranged in a hierarchical structure. Taxonomies are used to classify and logically connect content across multiple models. They are defined in the schema editor and support up to six levels of depth. Taxonomies are an enterprise feature. | ## Content stages This list offers a brief description of the two content stages every Hygraph project has by default. | Stage name | Description | |------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Draft | The working stage for content. Unpublished entries exist only here. Published entries also have a `DRAFT` copy for edits before re-publishing. Saving without publishing does not affect the live published version. | | Published | Published content. If the published button displays green, the content has been pushed live. If it displays blue, a new draft version has been saved and the published content is now outdated. | You can also create custom content stages for your project. See [content stages](/docs/developer-guides/content/content-stages) to find out more. ## Fields | Field type | Description | |-------------------|---------------------------------------------------------------------------------------------------------------------------------------------------| | Single line text | [String](/docs/api-reference/schema/field-types#string) type field widget. Used for adding headings and titles. | | Multi line text | [String](/docs/api-reference/schema/field-types#string) type field widget. Used for adding longer text such as descriptions. | | Markdown | [String](/docs/api-reference/schema/field-types#string) type field widget. A markdown editor. | | Slug | [String](/docs/api-reference/schema/field-types#string) type field widget. Used for adding URL-friendly identifiers to your content. | | Rich text | [Text](/docs/api-reference/schema/field-types#rich-text) type field widget. A text editor with formatting. | | Number | [Integer](/docs/api-reference/schema/field-types#integer) type field widget. Used for adding numeric fields such as IDs or identifiers. | | Float | [Float](/docs/api-reference/schema/field-types#float) type field widget. Used for numbers that include decimal values, such as ratings or prices. | | Boolean | [Boolean](/docs/api-reference/schema/field-types#boolean) type field widget. A toggle that represents true/false. | | Date | [Date](/docs/api-reference/schema/field-types#date) type field widget. A calendar date picker. | | Date and time | [Date and time](/docs/api-reference/schema/field-types#date-and-time) type field widget. A calendar date picker that includes time. | | JSON editor | [JSON](/docs/api-reference/schema/field-types#json) type field widget. Allows adding arbitrary JSON structures. | | Cloudinary asset | [JSON](/docs/api-reference/schema/field-types#json) type field widget. Allows picking assets from Cloudinary. | | Asset picker | [Asset](/docs/api-reference/schema/field-types#asset) type field widget. Allows selecting assets from your project and supports any file type. | | Color | [Color](/docs/api-reference/schema/field-types#color) type field widget. Supports HEX and RGBA. | | Map | [Location](/docs/api-reference/schema/field-types#location) type field widget. Allows adding geo coordinates to your content. | | Dropdown | [Enumeration](/docs/api-reference/schema/field-types#enumerations) type field widget. Allows adding a dropdown list of values to your content. | | Basic component | Component type field widget. Used for a single fixed component type and can be configured for one or multiple instances. See [components](/docs/developer-guides/schema/components) to learn more. | | Modular component | Modular component type field widget. Used for multiple component types. See [components](/docs/developer-guides/schema/components) to learn more. | | Reference | [Relation](/docs/api-reference/schema/field-types#reference) type field widget. Used for content relationships. | | REST | [REST](/docs/api-reference/schema/field-types#rest-remote-field) type field widget. Used to add external data via REST. | | GraphQL | [GraphQL](/docs/api-reference/schema/field-types#graphql-remote-field) type field widget. Used to add external data via GraphQL. | ## Sidebar widgets | Widget name | Description | |-------------------|------------------------------------------------------------------------------------------------------------| | Entry information | System sidebar widget, added to your model by default. Offers basic entry information. | | Localizations | System sidebar widget, added to your model by default. Allows viewing localized fields. | | Preview | System sidebar widget. Allows previewing content changes by adding a preview link to your content. | | Releases | System sidebar widget. Allows grouping entries into content releases for coordinated publishing. Scheduled publishing is configured via publish actions and is an Enterprise feature. | | Stages | System sidebar widget, added to your model by default. Allows viewing in-app content stages. | | Versions | System sidebar widget, added to your model by default. Allows viewing the version history of your content. | See [system fields](/docs/api-reference/schema/system-fields) to learn more. ## Components | Concept | Description | |--------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Component | A component is a predefined set of fields that can be reused across models and content entries. | | Component field | A special field type in your Hygraph schema that defines which components of which type can be used in a model. Component fields can be of basic or modular types. | | Component instance | A specific occurrence of a component type, containing content, inside a content entry. | | Nested components | Functionality that allows you to create components within a component. | See [components](/docs/developer-guides/schema/components) to learn more. ## Variants | Concept | Description | |------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Base entry | The main content entry that variants are built from. Updating a variant-enabled field in the base entry does not update the same field in its variants, and vice versa. | | Segment | A named reference used to target a group of users based on shared characteristics or behaviors. Segments are managed externally and referenced in Hygraph to determine which variant of a content entry is delivered to a given audience. | | Variant | A personalized version of a content entry tailored to a specific audience segment. Variants inherit the structure of the main entry but allow certain fields to be customized independently, enabling content teams to deliver targeted experiences without duplicating entries. Variants are an enterprise feature. | See [variants](/docs/developer-guides/schema/variants) to learn more. --- # Add asset entry Source: https://hygraph.com/docs/developer-guides/assets/add-asset-entry ![Assets - Add Entry](/images/docs/user-guides/assets-add-entry.png) To add an asset entry, navigate to the **Assets** tab of your project and click on the `+ Add entry` button, located at the top right corner of your screen. Unlike our [regular asset upload](/docs/developer-guides/assets/upload-asset), when you add an asset entry you can add metadata to your asset. `Asset` is a system model that appears by default in every Hygraph project. You can edit the asset model to include the data you wish to have in your asset entries. This asset model configured in your project's schema will determine the data you can add to an asset entry. ![Assets - Add Entry](/images/docs/user-guides/assets-add-entry-screen.png) Besides the metadata, the `+ Add entry` screen has an `Upload` button that displays the same options as our [regular asset upload](/docs/developer-guides/assets/upload-asset). --- # Upload asset Source: https://hygraph.com/docs/developer-guides/assets/upload-asset [Check out our API Reference document on uploading Assets](/docs/api-reference/assets/uploading-assets) ![Upload asset](/images/docs/user-guides/asset-upload.png) To upload an asset, navigate to the **Assets** tab of your project and click on the `Upload` button, located at the top right corner of your screen. A screen will pop up, prompting you to select an asset source: ![Upload asset](/images/docs/user-guides/asset-upload-screen.png) For now, the system allows you to drop files on the screen, or browse them from your computer. You can add assets from one or more of these sources. As you select multiple assets, the pop up screen will display them as thumbnails. ![Upload asset](/images/docs/user-guides/asset-upload-thumbnails.png) You will have the option to add more, or upload the ones you've already selected. --- # Work with assets Source: https://hygraph.com/docs/developer-guides/assets/work-with-assets [Check out our API Reference document on Assets](/docs/api-reference/assets/assets-overview) ## Overview The assets manager contains all documents (images, videos, etc.) that have been uploaded to a project. On this screen, you can sort and filter assets, configure the columns and save views just like in the **Content editor**, perform single or bulk actions, schedule asset entry releases, and upload assets. Let's look into these in more detail. ## Switch views You can choose whether you want your assets to display in a grid or table view. You can find the switch at the top of the screen: ![Switch asset views](/images/docs/user-guides/assets/asset-table-views-switch.png) ### Table view This is what the table view looks like: ![Assets - Table view](/images/docs/user-guides/assets/asset-list-view.png) Users can open the asset entry by clicking on a table item, or use the checkboxes to perform single or bulk actions. ### Grid view This is what the grid view looks like: ![Assets - Grid view](/images/docs/user-guides/assets/asset-table-grid-view.png) - **Open an entry:** Open an asset entry by clicking anywhere on the thumbnail or the title. - **Preview asset:** Click on the eye icon on an asset card to open a large asset preview. This icon appears on hover. - **Asset selection:** Select boxes appear on hover. Selecting an asset activates selection mode. As a result: - A deselect all button appears at the top of the grid. - All checkboxes become visible on all assets. - Clicking on asset cards will not open the asset in selection mode. ## Sort asset entries ![Sort asset entries](/images/docs/user-guides/assets/asset-table-grid-view-sort.png) Click on the `Sort by` dropdown menu at the top-left of the table to select a sorting option. ## Filter asset entries ![Filter asset entries](/images/docs/user-guides/assets/asset-table-grid-view-filters.png) Click `Filter` at the top of the assets table to display the filters menu. You can find detailed information on how to work with filters [here](/docs/developer-guides/content/using-filters). ## Bulk actions If you select asset cards using the checkboxes, the bulk actions will display at the bottom of the table. ![Bulk actions](/images/docs/user-guides/assets/asset-table-grid-view-select-all.png) The actions panel contains the following: - `Show selected`: Clicking on this option displays only the selected asset entries on the list. - `Publish`: This option starts the publishing process. Clicking on `Publish` prompts a pop up screen where you can use checkboxes to select whether non localizable fields and/or the default localization fields will be published, before clicking on the `Publish` button to finish the process. You can publish entries in batch by selecting multiple entries then clicking on `Publish`, but please consider that when using batch-publishing, non-localized fields and the default localization will always be published. - `Unpublish`: Clicking on `Unpublish` unpublishes the selected entries. - `Delete`: Clicking on `Delete` results in the elimination of the selected entries. Please consider that this change is permanent and cannot be rolled back. - `Schedule`: Clicking on `Schedule` prompts a pop up screen where you can schedule publication by selecting one of the following options: - `Schedule entry only`: This option schedules an entry individually. After selecting this option, you'll select an `Action` (publish, or unpublish), a date and time, and a time zone. You can also use checkboxes to select which additional locales are published/unpublished - non-localized fields and the default localization will always be published. - `Add to an existing release`: This option allows you to select from existing releases that need to have been previously configured in your content editor. If you have configured releases, then you will be able to select one from a dropdown menu after selecting this option. Otherwise, the following message displays: **No releases available, please add one**. - `Add to a new release`: This option allows you to create a new release and add the current entry to it. After selecting this option and giving your new release a `Name` and `Description`, you'll select an `Action` (publish, or unpublish), a date and time, and a time zone. You can also use checkboxes to select which additional locales are published/unpublished. - **To select all asset cards**, select the `Select all` checkbox at the top of the table. - **To clear selection**, simply deselect the `Select all` checkbox at the top of the table. - The actions panel will also display if you select a single entry, but bear in mind that the actions will only impact the selected entry. ## Edit asset entries To edit the details of an asset entry click on the asset card if you're seeing the grid view, or on an asset list item if you're seeing the table view. ![Edit asset entries](/images/docs/user-guides/assets/edit-asset-entry.png) The edit view of asset entries allows you to edit the asset itself as well as its metadata. To edit the metadata attached to an asset, simply edit the fields as you would do in the **Content editor**. You will find three buttons on top of the asset image: - `Replace`: This option replaces the asset keeping the metadata attached to it. - `Download`: This option downloads the asset to your computer. - `Copy URL`: This option copies the asset URL. While you can change the document attached to an asset entry by clicking `Replace` in the edit view, please note that this will change the asset URL. Make sure to replace that URL if you're using it outside of the project. The entry `ID` will remain the same. ## Configure columns ![Configure columns](/images/docs/user-guides/assets/assets-configure-columns.png) You can use the cog icon at the top-right of the assets table to configure the visible columns. Clicking on it opens a dropdown where you can select or deselect columns using the eye icons. An open eye indicates that the is visible, while a closed eye means it is hidden. [Here](/docs/developer-guides/content/configuring-columns) you can find detailed information on how to configure columns. --- # Accessing created content Source: https://hygraph.com/docs/developer-guides/content/access-created-content To access already created content, click on the Content editor tab: ![Hygraph Content Editor](/images/docs/user-guides/hygraph-content-editor.png) Hygraph offers users a variety of ways to keep their content organized and accessible. Everyone has different preferences on how content should appear and how clean the UI should be. There are some simple settings that allow users to customize the look and feel of their content views. Here you will find some information on how to use [filters](/docs/developer-guides/content/using-filters), how to [configure columns](/docs/developer-guides/content/using-filters), and how to [customize your content views](/docs/developer-guides/content/content-views). --- # Configuring columns Source: https://hygraph.com/docs/developer-guides/content/configuring-columns Configuring columns will help you declutter your content views by hiding information that is not relevant to you. Click on the cog icon to display the options inside, and then select which columns you would like to show in your content table by clicking on the eye icon. An open eye means the column is shown, and a closed eye means it's hidden. You can also rearrange the position of the columns on the table by clicking on the six-dots handle next to each option and dragging them into the order you want. --- # Content localization Source: https://hygraph.com/docs/developer-guides/content/content-localization ## Overview Localization is supported as a core feature in Hygraph. It allows a single piece of content to hold many configured locales. [Check out our API Reference document on localization](/docs/api-reference/content-api/localization) ## What you can do In your project settings you can define how many locales you want to support, choosing from a range of common defaults to providing custom locales as well. Depending on the content model and the number of locales activated in the project several locales can be available. ![Localized content fields](/images/docs/user-guides/localized-content-fields.png) Locales function by creating localized fields on the document model, not creating a duplication of the content. You can stage, eventually version and evolve localized content entries with "per field" granularity. ## Adding a translation 1. Navigate to the Content Editor and select a view. 2. Create a new entry or, alternatively, edit an existing one. 3. Find `Localizations` on the right sidebar and click the `+` icon to add a locale variant to all fields in the content entry that support localization. ## Show or hide translated fields You can toggle the "eye" icon on the left of a locale on the contextual sidebar of a content entry to show / hide the localized fields. ![Toggle translated fields](/images/docs/user-guides/toggle-content-localization.png) An open eye means the localized fields are visible and a close eye means they are hidden. --- # Content stages Source: https://hygraph.com/docs/developer-guides/content/content-stages ## Overview Hygraph offers two system content stages (`DRAFT` & `PUBLISHED`) plus the possibility to create custom ones depending on your plan. [Check out our API Reference document on content stages](/docs/api-reference/content-api/content-stages) This document discusses what these stages mean and how you can use them to create workflows. **Content stages are environment specific**. This means their configuration is applied per environment. Take this into consideration if you're working with a project using more than one environment. ## What you can do - You can save content in the `DRAFT` stage while you work on it, before it's ready to be `PUBLISHED`. - You can use content staging to [compare versions](/docs/developer-guides/content/versioning-content) of content in different stages. - In case of an error, you can quickly revert changes to the `DRAFT` stage from the `PUBLISHED` stage, then use [version comparison](/docs/developer-guides/content/versioning-content) to compare the two and choose which fields to revert to the `PUBLISHED` stage. ## System content stages Hygraph has two system content stages, `DRAFT` and `PUBLISHED`. `DRAFT` allows people to add and edit content without affecting published content. When the content is ready to be pushed live, it is promoted to the `PUBLISHED` stage. - Content cannot be consumed by others unless it's published. - Depending on your plan, you can [create custom content stages](/docs/developer-guides/content/content-stages#custom-content-stages). - System content stages cannot be edited or deleted. ![System content stages](/images/docs/user-guides/system-content-stages.png) Content in Hygraph that has been saved but has never been published exists only in the `DRAFT` stage. When promoted to the `PUBLISHED` stage, content will exist in that stage, and also in `DRAFT`. When you update a content entry and save without publishing, that new content will be saved in `DRAFT` and the entry will show a blue `PUBLISHED` pill on the content table, indicating that it's **outdated**. Content being **outdated** means that the version currently saved in the `DRAFT` stage contains changes that have not yet been promoted to `PUBLISHED`. - Content will always be created in the `DRAFT` stage. Any actions that are not `publish` or `unpublish` will only affect the `DRAFT` version of a content entry. If you're using the UI, that action is `Save`; and if you're using the API, it's `create` or `update`. - A `DRAFT` pill in the content table indicates that the content has been saved but has never been published. - A green `PUBLISHED` pill in the content table indicates that the currently published version of the content is the latest one. - A blue `PUBLISHED` pill in the content table indicates that the `DRAFT` version is newer than the `PUBLISHED` version. This means you will need to publish this content before it can be consumed by others. ## Custom content stages Depending on your plan, you may only have access to the two system content stages that we described above, or you may have the possibility to create more. - Check out our pricing page for information on plans - [Click here to learn how to configure custom stages](/docs/developer-guides/project/configure-content-stages) Paid plans allow creating custom content stages, which you can use to create more sophisticated workflows. For instance, the following diagram shows a workflow with 3 content stages: `DRAFT`, `APPROVAL` and `PUBLISHED`. ![Hygraph workflow with 3 content stages](/images/docs/user-guides/workflow-3-content-stages.png) This feature can be particularly helpful when using **Custom Roles**. For example, if a team member should only be able to create `DRAFT` content but not move it directly to the `PUBLISHED` stage, this is possible with **Custom Roles**. Check out our [Roles & Permissions](/docs/developer-guides/project/roles-and-permissions#custom-roles) document to learn more about **Custom Roles**. --- # Working with content views Source: https://hygraph.com/docs/developer-guides/content/content-views ## Overview Content views are basically the way our content is displayed in the content editor. They are divided into: - **Default views:** Generated automatically. They show the default columns configuration and without any filter criteria applied. - **Custom Views:** Generated by users. They show a specific columns configuration and filter conditions, selected and saved by the user. ## What you can do ![Content views](/images/docs/user-guides/content-views-in-editor.png) - You can use content views to easily navigate the content in the **Content editor** - You can create custom content views to easily access certain content - You can create view groups to organize your custom content views ## Update default views When making changes to the content view - by configuring columns, or adding filters - you can save this configuration as the default view for all users of the project. To do this, click the `View` dropdown at the top-right of the screen to display the options inside, and select the `Update view` option. ![Update default view](/images/docs/user-guides/update-view-default.png) Default views are also reflected inside of connection models when linking other content models. ## Custom views The default content views are the system views which list all content entries that have been created based on the respective content models. Custom content views are filtered for specific content entries. After you customize your content table using the three features mentioned above, you can save these customizations using the Content View. This can be a helpful way of organizing your content and making it easier to find. For example, as a content editor if you are only editing Blog posts written by Annie, you can create a content view called “Annie's Blog Posts.” This will give you quicker access and let you avoid scrolling through content to find what you are looking for. ### Create a custom view 1. Navigate to the Content editor and select a model. 2. Create a change to the view by either filtering the content in the search bar at the top, or showing/hiding the columns. 3. Click the `View` dropdown at the top-right of the screen to display the options inside, and select the `Save as new view` option. 4. Provide a `Name` and, optionally, `Description`. 5. Click `Add custom view` to save. ### Update a custom view ![Update custom view](/images/docs/user-guides/edit-custom-view.png) 1. Navigate to a custom view in the Content editor. 2. Next to the title of the content view, click on the context menu to display the options inside and select `Update custom view`. 3. Apply your changes and click `Update` to save. ### Delete a custom view ![Delete custom view](/images/docs/user-guides/delete-custom-view.png) 1. Navigate to a custom view in the contextual sidebar of the content tab. 2. Next to the title of the content view, click on the context menu to display the options inside and select `Delete custom view`. 3. Confirm you want to delete. ## Organizing custom views ![Reorder custom views](/images/docs/user-guides/reorder-custom-views.png) 1. Hover your mouse next to a custom view. 2. Grab the six-dot handle to the left of the label. 3. Drag and drop to change the order. ## View groups Custom **View Groups** let you create a group of Content Views. These can be helpful when working in larger teams as you can create a Custom View Group that contains all of the content for each person. These custom View Groups let team members create their custom views and save them all in one place. In using these features to customize your content tables, teams will be able to set up best practices, optimize their time and experience in Hygraph and focus on creating great content. **View Groups** allow you to customize the presentation and selection of content visible to an editor. You can show or hide different columns and filter which content should be included in the group. This allows for an optimized editorial workflow, helping editors to quickly find the content that needs their attention. ![View groups](/images/docs/user-guides/view-group.png) ### Create a view group ![Create custom view group](/images/docs/user-guides/create-custom-view-group.png) 1. Navigate to the content editor. 2. Click on the folder icon next to **Content** on the contextual sidebar to add a new view group. 3. Provide the `Name`. 4. Click `Add view group` to save. ![Save custom view group](/images/docs/user-guides/save-custom-view-group.png) ### Edit a view group ![Edit a custom view group](/images/docs/user-guides/edit-custom-view-group.png) 1. Find a custom view group in the contextual sidebar. 2. Click on the context menu and select `Edit view group`. 3. Apply your changes and click `Save` to confirm. ### Delete a view group ![Delete a custom view group](/images/docs/user-guides/delete-custom-view-group.png) 1. Find a custom view group in the contextual sidebar. 2. Click on the context menu and select `Delete view group`. 3. Confirm the deletion. ## Organizing custom view groups ![Reorder custom view groups](/images/docs/user-guides/reorder-custom-view-group.png) 1. Hover your mouse next to a custom view inside a view group. 2. Grab the six-dot handle to the left of the label. 3. Drag and drop to change the order. --- # Creating content Source: https://hygraph.com/docs/developer-guides/content/creating-content [Check out our API Reference document on mutations](/docs/api-reference/content-api/mutations#create-entries) ## How to create an entry ![Creating content with required fields](/images/docs/user-guides/create-content.png) 1. Navigate to the **Content editor**. 2. Choose a content model from the collapsible content views sidebar. 3. Click `+ Add entry` at the top-right corner of the screen. 4. Fill out the information required by your content model. 5. Click `Save` or `Save & publish` according to your workflow. Clicking `Save` will save your content onto the `DRAFT` stage, while clicking `Save & publish` will save your content onto both the `DRAFT` and `PUBLISHED` stages, respectively. ![Save and Publish content](/images/docs/user-guides/save-and-publish-content.png) The content model defines which fields are required and which fields are not mandatory. Hygraph allows the set-up of additional [content stages](/docs/developer-guides/content/content-stages) (e.g. a `QA` and/or `ARCHIVED` stage) depending on your plan. The following diagram shows a simplified workflow including 3 content stages - `DRAFT`, `APPROVAL` and `PUBLISHED` - as well as two roles: **Editor** & **Editor Supervisor**. ![Hygraph workflow with 2 roles](/images/docs/user-guides/workflow-3-content-stages-2-roles.png) ## Field outline Our field outline feature helps you reduce the time it takes to create and edit content by providing you with a visual representation of form structures. As you work on a content entry, our field outlines will help you quickly and easily locate specific fields, even within long and complex content forms. The field outline sidebar displays to the left of your content form and, as you scroll over it, highlights on the content form will indicate the corresponding fields. Clicking on an item on the field outline sidebar will take you to that field in the content form. To do this with a component field, you need to click on the dropdown menu to display the component and its contents, then click on one of the fields inside the component. ## Related features ### Content stages Hygraph allows the set-up of additional [content stages](/docs/developer-guides/content/content-stages) (e.g. a `QA` and/or `ARCHIVED` stage) depending on your plan. The following diagram shows a simplified workflow including 3 content stages - `DRAFT`, `APPROVAL` and `PUBLISHED` - as well as two roles: **Editor** & **Editor Supervisor**. ![Hygraph workflow with 2 roles](/images/docs/user-guides/workflow-3-content-stages-2-roles.png) ### Connecting related content For content models that have [references](/docs/developer-guides/content/using-references), you can either connect an existing entry, or create and connect a new one. References are relations between two or more content entries that you can create in Hygraph. There are four different reference types according to reference cardinality, which determines if it will be possible to connect one or many entries to the parent entry, as well as the directions of that relation. [Check out our documentation on references to learn more](/docs/developer-guides/content/using-references). --- # Customize sidebar Source: https://hygraph.com/docs/developer-guides/content/customize-sidebar You can configure schema sidebar adding and/or removing system and custom widgets. ## Adding widgets ![Add sidebar widgets](/images/docs/user-guides/sidebar-widgets.png) 1. Navigate to the schema builder 2. Find the model you want to customize the sidebar for 3. Click on the `Sidebar` tab 4. Under **Sidebar Widgets**, click on the widget you want to add Depending on the widget, you may need to enter additional information before saving. ## Removing widgets 1. Navigate to the schema builder 2. Find the model you want to customize the sidebar for 3. Click on the `Sidebar` tab 4. Under **Sidebar Widgets in Use**, click on the context menu and then `Remove` ## Reordering widgets 1. Navigate to the schema builder 2. Find the model you want to customize the sidebar for 3. Click on the `Sidebar` tab 4. Under **Sidebar Widgets in Use**, click & hold the drag handle to reorder the widget, then drop it to the desired position --- # Deleting content Source: https://hygraph.com/docs/developer-guides/content/deleting-content [Check out our API Reference document on mutations](/docs/api-reference/content-api/mutations#delete-entries) ## How to delete content ![Content Quick Actions](/images/docs/user-guides/content-quick-actions.png) 1. Navigate to the Content editor. 2. Select a content model 3. Find the content item you want edit in the content entries table. 4. Check the selection box at the beginning of the row. 5. Press the trash icon with the word `Delete` at the bottom of the content table. 6. Confirm the deletion. ## Remove related content 1. Open the content entry that you want to disconnect a relation from. 2. Find the reference field. 3. Click on the context menu to the right of the content entry title and select `Delete`. ![Remove related content](/images/docs/user-guides/removing-related-content.png) --- # Duplicating content Source: https://hygraph.com/docs/developer-guides/content/duplicating-content ## Duplicate content entries Inside each content entry, editors have the option to duplicate content. Follow these steps to duplicate content: 1. Navigate to the content editor and select the view that contains the entry you want to duplicate. 2. Click on the entry to edit it. 3. Click on the duplicate icon at the top of the screen. 4. The duplicated entry loads. The word **(copy)** at the end of the title line indicates that you're currently in a copy of the original entry. You can now make the necessary changes to the content entry. 5. Click `Save` or `Save & publish` depending on your workflow. If you try to leave without saving, the system will warn you. The system adds the word **(copy)** at the end of fields marked as `Title` in the schema. ![Use as title field - Schema field configuration](/images/docs/user-guides/use-as-title-field.png) Check out our [Field configuration](/docs/api-reference/schema/field-configuration#use-as-title-field) document for more information about this. Note that if the content entry contains reference fields, some of the relations will be duplicated and others won't. The system will notify you if this is the case: ![References not duplicated](/images/docs/user-guides/references-not-duplicated.png) What is copied in this case is the reference - the link between the two content pieces - but not the referenced entry itself. The following table explains the system behavior depending on reference configuration: | Type | Direction | Cardinality | System behavior | | ----------------------------- | ----------------- | ------------ | ------------------------------------------------------------------------- | | Can reference only one model | One-way reference | To one | The system duplicates the reference. | | Can reference only one model | Two-way reference | One to one | The system **does not duplicate** the reference. You can add it manually. | | Can reference only one model | One-way reference | To many | The system duplicates the reference. | | Can reference only one model | Two-way reference | Many to many | The system duplicates the reference. | | Can reference multiple models | Two-way reference | One to one | The system **does not duplicate** the reference. You can add it manually. | | Can reference multiple models | Two-way reference | Many to many | The system duplicates the reference. | | Can reference multiple models | Two-way reference | Many to one | The system duplicates the reference. | The system will not duplicate references with the cardinality **one to one**. ## Duplicate relations When viewing content that has relations (references), you can quickly duplicate referenced entries within an original entry. This is a quick way to keep the format of the related content, while allowing you to make content edits that won't affect other entries that contain the same reference. ![Duplicating related content](/images/docs/user-guides/duplicating-related-content.png) 1. Navigate to the content entry that contains the relation you want to duplicate. 2. Click on the context menu and select `Duplicate`. 3. The duplicated reference screen will display. Here you can make the necessary edits, then click `Save` or `Save & Publish` depending on your workflow. 4. Click on the `X` at the top-left corner of the editor to return to the original content entry you were working on. The reference field now contains the duplicated reference. System behavior depends on field configuration: - If the field allows referencing only one model, the duplicate will replace the original. - If the field allows referencing more than one model, the duplicate will display as an additional reference. --- # Publishing content Source: https://hygraph.com/docs/developer-guides/content/publishing-content [Check out our API Reference document on mutations](/docs/api-reference/content-api/mutations#publishing-content-mutations) ## How to publish content Publishing content is in some ways the most important step of creating effective content. If it is not published, no one can consume the content that you've created. Hygraph has two [Content Stages](/docs/developer-guides/content/content-stages) as we've seen before, `DRAFT` and `PUBLISHED`. When you have finished working on a content piece and would like to move it out of the `DRAFT` stage and into the `PUBLISHED` stage, simply click `Save & Publish` or `Publish`. - If the latest changes have not yet been saved, the button will say `Save & Publish`. - If the latest changes have already been saved, the button will say `Publish`. One thing to always check is that all of the assets and references are also on the `PUBLISHED` stage so that everything loads properly. If Hygraph notices that assets or relations to your content are still in the `DRAFT` stage, clicking `Save & Publish` will prompt you to publish these too. When content is `PUBLISHED`, the system creates a new version so you can see how it has evolved and when it was last updated. If you have additional stages enabled, you will be presented with a dropdown to choose which stage you want to publish to. ![Publishing to a stage](/images/docs/user-guides/publish-to-content-stages.png) ## Edit published content Once published, any content can be edited in a similar fashion to [creating new content](/docs/developer-guides/content/creating-content). Simply navigate to the content piece you would like to modify, and use the content editor as before. When changes are made to the content, you will see the `Save & Publish` button has been reactivated, allowing you to publish the changes you've made. If you choose to retain the changes in the `DRAFT` stage and not publish them, you will need to click on the `Save` button instead. You will notice that the pill for that content in the content table has changed to blue, indicating [outdated content](/docs/developer-guides/content/publishing-content#outdated-content). This implies that changes have been made on the `DRAFT` stage that have not yet been promoted to the `PUBLISHED` stage. ## Outdated content With Hygraph it is possible to review posts that have been published beforehand, and have changes made on the `DRAFT` stage. ![Outdated Content Warning](/images/docs/user-guides/outdated-content-warning.png) When looking at the content view, a new blue pill indicates that a previously `PUBLISHED` content piece has changes that were saved on the `DRAFT` stage, but not on the `PUBLISHED` stage. Once reviewed and approved by the Editor, clicking `Publish` will promote the new changes to the `PUBLISHED` stage. To compare the changes made on `DRAFT` to the `PUBLISHED` state, simply navigate to the available versions in the right sidebar. ![Compare current draft](/images/docs/user-guides/compare-with-current-draft.png) Click [here](/docs/developer-guides/content/content-stages) to learn more about content stages. ## Scheduled publishing **Hygraph** allows you to schedule content publishing and unpublishing, as one off or as a collection - referred to as a "release". Check out our document on [scheduled publishing](/docs/developer-guides/content/scheduled-publishing) to learn more. --- # Rich Text editor Source: https://hygraph.com/docs/developer-guides/content/rich-text-editor ## Overview This content editor tool comes out-of-the-box for all Hygraph projects, and provides great flexibility when it comes to creating, editing, and manipulating content entries showcasing text and images. If your project schema is configured so that your model has a Rich Text field, you'll see it like this in the content editor: ![Rich Text editor](/images/docs/user-guides/rte/rte-ui-buttons.png) 1. This dropdown allows you to format text you've highlighted in the text area. You can format it as normal text as well as different heading levels. 2. These typography options allow you to make text bold, italic, or underlined. 3. Use this option to add a link. Click [here](/docs/developer-guides/content/rich-text-editor#links) for more information. 4. Use this option to format the text as a block quote. 5. Use this option to format the text as inline code. 6. This option allows you to include an asset. Click [here](/docs/developer-guides/content/rich-text-editor#assets) for more information. 7. Use these options to create either a bulleted or numbered list. 8. Use this option to format the text as a code block. 9. Click on the `Iframe` button to display a popup where you can enter an iframe URL. After you've entered a valid iframe URL, click on the `OK` button to add it to your content. 10. Use this option to assign classes to your text. Simply highlight the text and click on the `Class` button. A popup will display for you to type in the `Class name`, then click on `Ok` to finalize the process. 11. Use this option to add a table. 12. Use this option to format text as subscript. 13. Use this option to format text as superscript. 14. Use this option to enter the fullscreen rich text editor mode. 15. This option allows you to embed content from a model. Click [here](/docs/developer-guides/content/rich-text-editor#embeds) for more information. [Check out our API Reference document on Rich Text](/docs/api-reference/content-api/rich-text-field) ## Field configuration ![Rich Text editor configuration](/images/docs/user-guides/rte/rte-config.png) When you add a Rich Text field to a model in your schema, you will find the following options: | Tab | Field Name | Description | | ----------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Settings | `Display Name` | Display name of your rich text field. | | Settings | `API ID` | API ID of your Rich Text field. This field is autocompleted as you type in the display name, but can be edited. | | Settings | `Description` | Optional field. You can add a description for your Rich Text field here. | | Settings | `Enable embedding` | Select this checkbox to enable embedding, which will allow you to reference content inside rich text. You cannot edit this setting after the initial save. | | Settings | `Allow multiple values` | Select this checkbox to store a list of values instead of a single one. | | Settings | `Localize field` | Select this checkbox to make the Rich Text field localized. | | Settings | `Formatting options` | Use this dropdown menu to customize which buttons will appear in your rich text editor. You can read more about this menu [here](/docs/developer-guides/content/rich-text-editor#customize-your-editor). | | Validations | `Make field required` | Select this checkbox to make the rich text field required. If you select it, you won't be able to save the entry if the field is empty. This option cannot be edited after the initial save. | | Validations | `Set field as unique` | Select this checkbox to set this field as unique. Selecting it ensures that multiple entries can't have the same value for this field. | | Advanced | `Set initial value` | Select this checkbox to set an initial value to prefill the form input. | | Advanced | `Show based on condition` | Select this checkbox to set condition visibility for this field. [Check out our conditional fields document to learn more](/docs/developer-guides/schema/conditional-fields). | | Advanced | `Field visibility` | Use this dropdown to select a field visibility option. The options are: read/write, read only, hidden, and API only. | ### Customize your editor When you configure your Rich Text field, you will find the `Formatting options` under **Settings**. ![Rich Text editor configuration](/images/docs/user-guides/rte/rte-config-buttons.png) This dropdown menu lets you select which buttons will display on your rich text field in the **Content editor**. ![Rich Text editor configuration](/images/docs/user-guides/rte/rte-config-buttons-displayed.png) By default, all checkboxes are selected. Deselect the buttons that you don't want in your rich text editor. You can change your formatting options after the initial save. To do this, simply edit your Rich Text field and select/deselect the checkboxes according to your needs. ## Using the Rich Text editor ### Links Click on the `Link` button to display the **Insert link** popup, which contains two tabs: **Settings** and **Attributes**. #### Settings ![RTE - Insert link - Settings tab](/images/docs/user-guides/rte/rte-links-settings.png) - **Link type:** Use the radio buttons to select one of the two options. Your selection modifies the fields displayed on screen. - **URL:** Select this option to add a link to an external source. - **Entry:** Select this option to add a link to an entry in your Hygraph project. - **Link text:** You can add text for your link in this field. If you are creating a `URL` link type and don't complete this field, the **Link address** will be used as **Link text**. This field becomes mandatory for the `Entry` link type. If you selected text before clicking on the link button, this field will be autocompleted with the selected text. - **Link address:** Mandatory field where you can type the URL you wish to link to. This field only displays for the `URL` link type. - **Open link in new tab:** Use this checkbox for your link to open in a new tab. It adds the `target='_blank'` attribute to your link. This field only displays for the `URL` link type. - **Link title:** Optional field where you can enter a tooltip to display on hover. This field only displays for the `URL` link type. - **Select entry:** Click on `Add existing entry` to select the entry in your Hygraph project that you wish to create a link to. Only models that can be embedded will display in the dropdown. This field only displays for the `Entry` link type. - **Select entry** with an **Asset** creates a text-based link that references the asset entry. It does not wrap an inserted or rendered image in a link. - **Action buttons:** - **Cancel:** Click on this button to cancel the process. - **Insert:** Click on this button to save the link and add it to your content entry. #### Attributes You can optionally complete the following `HTML` attributes: ![RTE - Insert link - Attributes tab](/images/docs/user-guides/rte/rte-links-attributes.png) - **Rel attribute:** This attribute defines the relationship between the current document and the linked resource. - **Class attribute:** This attribute specifies one or more `classnames` for an element. You can add classes separated with spaces here. - **ID attribute:** This attribute is used to specify a unique `id` for an `HTML` element. - **Action buttons:** - **Cancel:** Click on this button to cancel the process. - **Insert:** Click on this button to save the link and add it to your content entry. ### Assets Clicking on the `Assets` button opens the **Select Asset** screen: ![RTE - Select assets](/images/docs/user-guides/rte/rte-assets-select.png) Select an asset by clicking on the purple reference icon located at the beginning of each asset entry. This will automatically add the asset to your content. If the asset you need has not yet been uploaded to your project, you can do so from here by click on the `Upload` button. This process follows the standard [asset upload flow](/docs/developer-guides/assets/upload-asset). ### Embeds Use this option to create **inline** and/or **block embeds**. **Inline embeds** give Content Creators maximum flexibility to add references or relations directly within the text so they can move faster. This also clears up clutter around content creation, so they are able to add things like authors, citations, product attributes and more without having to add extra fields within the content entry. **Block embeds** give Content Creators more flexibility with less clutter. Use block embeds to insert assets and other models inside the text as a block. This can be useful when adding images like logos, or referencing audiences to previous created material that support the new content item like blog posts, documents, announcements, etc. ![RTE - Embeds - Block embed screen example](/images/docs/user-guides/rte/rte-block-embed.png) Only the content from the selected models in the Rich Text field configuration is available to be embedded. To be able to embed content from more models, enable them from the Rich Text field settings in the schema builder. Whether you add an inline or a block embed, both screens show the same options: Use the dropdown to select the model you wish to embed content from, then click on `Select model`. The system will display the selection screen for the chosen model. To select an entry and add it to your content, simply click on the purple reference icon located at the beginning of each content entry. To `Edit` or `Remove` an embed you've added to your Rich Text editor, click on the context menu icon of your embed and select the desired option. ## Resources - [Rich Text field API Reference:](/docs/api-reference/content-api/rich-text-field) Guide for developers on how to work with the Rich Text field. - Introducing the Hygraph React Rich Text Renderer: Blog post on how to render Hygraph documents using Rich Text in your application easily using our available packages. - Styling Rich Text with TailwindCSS: Detailed tutorial on how to use the `JSON` representation from the RTE to create custom elements for each text-based element of Rich Text. --- # Scheduled publishing Source: https://hygraph.com/docs/developer-guides/content/scheduled-publishing ## Overview This feature is only available for **Enterprise** plans. Please [contact sales](/contact) for further information. Hygraph gives you the ability to schedule content publishing, and unpublishing as a one-off or as a collection,referred to as a "release". | Section | Description | | ----------------- | ---------------------------------------------------------------------------- | | Scheduled entries | Overview of all individually scheduled items that are not part of a release. | | Releases | Overview of all releases, and their scheduled items. | Scheduled entries in the content table display a stage pill with a clock on it: ![Scheduled entries in the content table](/images/docs/user-guides/scheduled-entries-pill.png) Hovering over the pill will show you schedule publishing information for that entry. ![Scheduled entries in the content table](/images/docs/user-guides/scheduled-entries-pill-hover.png) ## How to schedule When editing a content entry, instead of publishing right away, you can **Schedule** an entry for publishing at a later date & time. You can find this option inside of the dropdown on the **Publish** button. ![Schedule Item Dropdown](/images/docs/user-guides/schedule-item-dropdown.png) The screen will guide your through the scheduling process, where you can create a one-off scheduled release, bundle it with other scheduled entries, or create a new release to add it. ![Scheduling Options](/images/docs/user-guides/schedule-item-modal.png) ### Schedule entry only You can publish (or unpublish) individual content entries at a specified date and time by selecting the `Schedule entry only` option. ![Schedule Item](/images/docs/user-guides/schedule-item.png) 1. Use the radio buttons to select the `Action` - if the entry should be published, or unpublished. If the project has more than the two default stages - `PUBLISHED` & `DRAFT` - this section will also show the `Stage to publish` dropdown, which you can use to select which of the project stages to publish to. ![Stage to publish](/images/docs/user-guides/stage-to-publish.png) The `Unpublish` action moves content to the `DRAFT` stage. 2. Click on the calendar icon under `Date and time` to select when the entry should be published. Alternatively, you can manually type in the date in the format that's visible in the placeholder of the field. 3. Use the `Timezone` dropdown to select one of the available timezones. 4. Use the checkboxes to select which localizations should be included. Non-localized fields and the default locale are required here, but you can choose which of the additional locales to publish or unpublish. 5. If you have any references linked to the entry you are scheduling, you can schedule those individually too. ![Schedule Related Entries](/images/docs/user-guides/schedule-relations.png) ### Add to an existing release You can bundle multiple content entries into a single release that will be published (or unpublished) at a specified date and time. Your releases will be shown if you choose to **Add item to a release**: ![Schedule release](/images/docs/user-guides/schedule-release.png) 1. Use the `Release` dropdown to select the release you want to add this entry to. 2. Use the radio buttons to select the `Action` - if the item should be published, or unpublished. If the project has more than the two default stages - `PUBLISHED` & `DRAFT` - this section will also show the `Stage to publish` dropdown, which you can use to select which of the project stages to publish to. ![Stage to publish](/images/docs/user-guides/stage-to-publish.png) The `Unpublish` action moves content to the `DRAFT` stage. 3. Use the checkboxes to select which localizations should be included. Non-localized fields and the default locale are required here, but you can choose which of the additional locales to publish or unpublish. ### Add to a new release You can create a new release to add the current entry to by selecting `Add to a new release`: ![Schedule release](/images/docs/user-guides/add-to-new-release.png) 1. Give the new release a `Name` and, optionally, a `Description`. 2. Use the radio buttons to select the `Action` - if the entry should be published, or unpublished. If the project has more than the two default stages - `PUBLISHED` & `DRAFT` - this section will also show the `Stage to publish` dropdown, which you can use to select which of the project stages to publish to. ![Stage to publish](/images/docs/user-guides/stage-to-publish.png) The `Unpublish` action moves content to the `DRAFT` stage. 3. Click on the calendar icon under `Date and time` to select when the entry should be published. Alternatively, you can manually type in the date in the format that's visible in the placeholder of the field. 4. Use the `Timezone` dropdown to select one of the available timezones. 5. Use the checkboxes to select which localizations should be included. 6. If you have any references - such a images - linked to the entry you are scheduling, you can use the checkboxes to schedule those too. ![Schedule Related Entries](/images/docs/user-guides/schedule-relations.png) ## Scheduled entries You can find the scheduled entries table in `Content > Schedule > Entries`. ![Scheduled entries table](/images/docs/user-guides/scheduled-entries-table.png) If you have no scheduled entries, the screen displays an empty state containing a link to the content editor, where you can select entries to schedule. ### View scheduled entries Navigate to `Content > Schedule > Entries` to view the table of scheduled entries. ![View scheduled entries](/images/docs/user-guides/view-scheduled-entries.png) The table contains all individual entries that have been scheduled. It can be sorted by newest, with most recently scheduled entries displaying at the top of the table, or oldest. To sort entries, click on the `Target date` column header. ![Scheduled entries - Target date](/images/docs/user-guides/scheduled-entries-target-date.png) You can filter the entries table by publication status: All, Completed, Failed, and Pending. To filter entries, use the dropdown at the top of the table. ![Scheduled entries - Filter by status](/images/docs/user-guides/scheduled-entries-sort-status.png) If the process fails, you can scroll over the `Failed` status to get more information: ![Scheduled publication failed](/images/docs/user-guides/scheduled-failed.png) The modal gives you the option to copy the error or contact support. ### Remove scheduled entries Navigate to `Content > Schedule > Entries` to remove scheduled entries. Use the checkboxes to select one or more entries, and click `Remove` on the action bar that display at the bottom of the table. ![Remove scheduled entries](/images/docs/user-guides/remove-scheduled-entries.png) You will need to confirm this action, as deletion cannot be rolled back. ## Scheduled releases You can find the scheduled releases table in `Content > Schedule > Releases`. ![Scheduled releases table](/images/docs/user-guides/scheduled-releases-table.png) If you have no scheduled releases, the screen will display an empty state prompting you to add a release. ### Add a release If you want to schedule a group of entries to be published (or unpublished) at the specified day and time, you can add a release. To do this, navigate to `Content > Schedule > Releases` and click **+ Add release**: ![Releases overview](/images/docs/user-guides/releases-overview.png) You'll need to give it a name, and an optional description. At this time, you can also use the dropdown menus to select a date & time, and a timezone: ![Create release](/images/docs/user-guides/create-release.png) To finalize the process, click `Add` to save. The scheduled release will display on the releases table. Any changes to your `DRAFT` content will be included with any pending scheduled release(s). ### Edit a release To edit a release, navigate to `Content > Schedule > Releases`, select the context menu of one of the releases on the table, and click `Edit`. ![Edit a release](/images/docs/user-guides/scheduled-release-edit.png) Simply edit the details as needed, and then click `Update`. ### Delete a release To delete a release, navigate to `Content > Schedule > Releases`, select the context menu of one of the releases on the table, and click `Delete`. ![Edit a release](/images/docs/user-guides/scheduled-release-delete.png) Since it's not possible to roll back deletions, you'll need to confirm this action to finalize the process. ## Limits - The following limits are set per your plan and can be customized: - A maximum of 50 pending releases, and 50 items in a single release. - A maximum of 200 pending scheduled items overall within your project environment. - The date and time selected for a release can't be in the past. --- # Searching for content Source: https://hygraph.com/docs/developer-guides/content/searching-for-content The most basic feature for navigating the content is using the search. The Search function enables you to quickly find any pieces that match the keyword entered. Users are able to search for any keyword (string fields) used in the content piece, such as Author, Title or Content Keywords. ![Search for content](/images/docs/user-guides/search-filter.png) By submitting a search term, a fresh request is generated including a `where` argument. Specifying a field type to narrow down the search can be done by typing the first letters or selecting it accordingly in the filter bar. The search itself is not case sensitive, meaning `TEST` or `test` will return the same results. Hygraph does not support Full Text Search currently. Rich Text or JSON is currently not supported. It's also not possible to filter or search for multi-value fields, colors, and coordinates. The default order of the items is by "created at", starting with the oldest item. Sorting by "created at" will allow you to change the order. ```graphql { posts(where: { _search: "Test" }) { id } } ``` In addition, it is possible to specify the following field types as part of the search request: - String (Single-Line, Multi-Line, Slug, Markdown) - also multi-value versions of these - Float - Integer An editor can verify the schema of the content model to understand which field types are used. However, the usable field types will be shown in the search bar as part of the filters and depend on the schema modeling. The advantage of specifying a field type lies in a performance improved search - which can be useful on larger content projects. The following field types that can be used as additional filters. - Enumeration - Boolean Mixing the different searches and filters will create more granular results. Note that conditions of all filters apply, meaning that the search query is of an AND-type. Editors also have the capability to save the search/filter as a content view. ## Additional resources - [Using filters](/docs/developer-guides/content/using-filters): Learn how to filter content by entry, relations, and content stage. --- # Updating content Source: https://hygraph.com/docs/developer-guides/content/updating-content [Check out our API Reference document on mutations](/docs/api-reference/content-api/mutations#update-entries) ## How to update content 1. Navigate to the **Content editor**. 2. Select a content model. 3. Find the content item you want to edit on the table and click on it to enter content editing mode. 4. Edit the content and then click `Save` to save the newly edited content in the `DRAFT` stage, or click `Save & Publish` to move the content to the `PUBLISHED` stage. Content in Hygraph that has been saved but has never been published, exists only in the `DRAFT` stage. When promoted to the `PUBLISHED` stage, content will exist in that stage, and also in `DRAFT`. When you update a content entry and save without publishing, that new content will be saved in `DRAFT` and the entry will show a blue `Published` pill on the content table, indicating that it's **outdated**. Content being **outdated** means that the version currently saved in the `DRAFT` stage contains changes that have not yet been promoted to `PUBLISHED`. --- # Using filters Source: https://hygraph.com/docs/developer-guides/content/using-filters ## Overview Filtering allows you to restrict the content shown by specific parameters. This is helpful when trying to create a more curated overview of content. Hygraph does not currently support filtering for Rich Text (even if inside components), JSON, multi-value fields, colors, or coordinates. These filters can be useful when building content views, looking for a specific type of content, or finding content that matches several parameters, such as "`Created by` a certain user & `Stage` published". [Check out our API Reference document on filtering](/docs/api-reference/content-api/filtering) ## Quick Filters Our **Quick Filters** feature reduces the time and effort required to create a filter. Editors can now find the content they need, faster. ![Quick Filters](/images/docs/user-guides/using-filters/quick-filters.png) It offers several predefined filters that apply instantly when selected, allowing editors to focus on their work without unnecessary distractions. Quick Filters are available in both regular content views and asset views. **To add Quick Filters**, simply use the `Filter` menu to display the available options and select one. ![Add a Quick Filter](/images/docs/user-guides/using-filters/add-quick-filters.png) Once you select a Quick Filter, the regular filter appears in the filter bar and the content table is sorted accordingly. You can apply more than one Quick Filter. To do this, simply use the filter menu again and select another one. For instance, the following image shows a content table where two Quick Filters have been applied, `Created by me` & `Only published entries`: ![Quick Filter applied](/images/docs/user-guides/using-filters/quick-filter-applied.png) Quick filters are commonly used filter configurations that offer a shortcut to finding the content you're looking for. Once applied, you can use them as they are, or [edit them](/docs/developer-guides/content/using-filters#adjust-filters-manually). ## Column filters The `Filter` menu also contains column filters. ![Column filters](/images/docs/user-guides/using-filters/column-filters.png) Column filters allow you to filter content by some system and custom fields, stage and ID. Quick and column filters function in the same way. [The following document section](/docs/developer-guides/content/using-filters#adjust-filters-manually) explains how to use them. ## Adjust filters manually You can find the filters on any content view, at the top of the content table. If there are no filters added, this is how it will look like: ![Filter Bar](/images/docs/user-guides/filterbar.png) **To apply filters:** 1. Navigate to the **Content Editor** & select a view from the collapsible sidebar. 2. Click the `Filter` button at the top of the content table. 3. Select one of the fields listed for filtering. It can be a quick or column filter. - **If you selected a quick filter**, the results will show on the table below. - **If you selected a column filter**, you need to configure it: - Select a condition. [This document section explains what each condition does](/docs/developer-guides/content/using-filters#filtering-conditions). - Enter a comparison value in the input to the right of the filter method. You can use the `Search` at the top of the filters menu to quickly find a filter. For example, the following image shows that we have selected to filter by `ID`, then selected the condition `contains`, and finally we typed "002" in the input. As a result, the filter shows all entries that have an ID containing 002: ![Filter example](/images/docs/user-guides/filter-example.png) **To remove a filter**, simply click the `X` button next to it. ![Remove filter](/images/docs/user-guides/remove-filter.png) ### Filtering by entry After applying a quick or column filter, the results will display on the table below. The following example shows the `Created By` filter applied: !["Created By" filter applied](/images/docs/user-guides/createdby-filter.png) You can adjust these filters manually by using the input fields or dropdown menus, and you can add additional filters by clicking on the filter icon. ![Apply a Filter](/images/docs/user-guides/filters-icon.png) Additionally, you can also filter by fields that allow multiple values. Here's an example showing an enumeration field for product size: ![Multi-value enum filtering](/images/docs/user-guides/multivalue-filter.png) ### Filtering by relations To filter your content entries by a reference field, click the `Filter` button and select the reference field from the column filters list. **It is not possible to filter by union-type reference fields** - those that allow you to reference more than one model in a field. To configure the filter: 1. Select the field of this model that you want to use. 2. Select a [condition](/docs/developer-guides/content/using-filters#filtering-conditions) from the dropdown menu. 3. Some conditions will require that you type in some data to filter by, such as a name or a slug. `Updated At` and `Created At` require that you select a date from a calendar and type in a time. `Is null` and `Is not null` do not require any additional input. The following example [filtering condition](/docs/developer-guides/content/using-filters#filtering-conditions) shows `all` of the content entries where the "Other products" reference field links to entries with a `Slug` that contains `tote`. ![Filtering by relations](/images/docs/user-guides/relational-filter.png) The default reference filters that all projects have are `Created by`, `Updated by`, and `Published by`. They are related to project users and provide relational fields that you can select from the condition dropdown. You can also filter by custom reference fields created for your project, as long as they are not union fields. [Our Editor Guides contain detailed information all reference filters available.](/docs/editor-guides/content/filter-content#reference) ### Filtering by content stage To filter content by stage, click the `Filter` button at the top of the content table, and select `Stage` from the column filters list. After this, use the dropdown menus to select a condition and stage. ![Filtering by content stage](/images/docs/user-guides/content-stage-filtering.png) #### Stages and conditions While there are three default stages - `DRAFT`, `PUBLISHED`, and `PUBLISHED (!)` - stages other than the default ones can exist in the projects of Growth and Enterprise plans. | Stage Name | Definition | | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `DRAFT` | Content entries that exist in `DRAFT`, and don't exist in any other stage. This content has never been published. | | `PUBLISHED` | Content entries that exist in `PUBLISHED` and are not outdated. This means that the published version of this content is also the latest version on the system. | | `PUBLISHED (!)` | Content entries that exist in `PUBLISHED` but are outdated. This means there is a version of this document currently published, but there is also another more recent version in the system. | Please take into account that our stages are not linear and sequential. There are cases where content can be in more than one stage at the same time, such as PUBLISHED and QA. This would happen if, for instance, you edit an already published document, but select the QA stage from the dropdown upon publishing your changes. | Condition | Definition | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `is` | Select this condition to fetch content entries that only exist in the selected stage. | | `is not` | Select this condition to fetch content entries that do not exist in the selected stage, and only exist in one stage. | | `contains` | Select this condition to fetch content entries that exist in the selected stage, irrespective of also existing in other stages. **This condition will only display for projects with more than two stages**. | | `doesn't contain` | Select this condition to fetch content entries that don't exist in the selected stage, irrespective of also existing in other stages. **This condition will only display for projects with more than two stages**. | Filtering can also be applied to your project's custom filters. Custom filters creation will result in a state that can be updated or outdated, and the behavior will be the same as the updated/outdated behavior shown above for `PUBLISHED` and `PUBLISHED (Outdated)`. ## Filtering conditions The following list explains basic conditions and what they do: | Option | Description | | -------------------------- | --------------------------------------------------------- | | `Is` | Exact match | | `Is Null` | Matches empty (without content) | | `Is Not Null` | Matches not null (with any content added) | | `Is Not` | Exclusion match | | `Less Than` | for numeric fields (including date and dateTime) | | `Less Than or Equal To` | for numeric fields (including date and dateTime) | | `Greater Than` | for numeric fields (including date and dateTime) | | `Greater Than or Equal To` | for numeric fields (including date and dateTime) | | `Contains` | String search | | `Doesn't Contain` | Exclusion by string search | | `Starts With` | Starting string match | | `Doesn't Start With` | Exclusion by starting string match | | `Ends With` | Ending string match | | `Doesn't End With` | Exclusion by ending string match | | `Includes some` | for multi-value fields, some of the provided values match | | `Includes all` | for multi-value fields, all of the provided values match | | `Includes none` | for multi-value fields, none of the provided values match | [Our Editor Guides contain detailed information about all the different types of filters available.](/docs/editor-guides/content/filter-content#general-filter-conditions) --- # Using references Source: https://hygraph.com/docs/developer-guides/content/using-references ## Overview References connect two or more content entries in Hygraph. This document gets into how to use the different reference types in the Hygraph app. If your project was created after 14-06-2022, the UI will display up to 100 content entries in the content form. For projects created before that, it will display up to 500. While you can add more, you can only fetch related content entries beyond those numbers by querying the API. ## What you can do - You can use the reference field in the **Content editor** to connect content entries to one another according to schema configuration. Examples of this could be linking an **Author** to a **Blog Post**, or a **Category** to a **Product**. ## Reference types There are four types of references based on how many entries you can link and in which direction: - **One-to-one references:** They connect one entry to one other entry from a specific model. For example, a country can have one capital, and a capital belongs to only one country. - **One-to-many references:** They link one entry to multiple entries from a specific model. For example, an author can be linked to many blog posts. - **Many-to-many references:** They allow multiple entries to connect to multiple other entries, but only within the allowed models. For example, a product can belong to multiple categories, and a category can contain multiple products. - **Many-to-one references:** They let multiple entries from specific models link to a single entry from a configured model. For example, many blog posts can reference the same author. When configuring a reference field, you must choose which content models can be linked. You cannot reference just any entry. Learn more about this in our [reference configuration](/docs/developer-guides/schema/references) documentation. ## How to use references References are used in similar ways, irrespective of their type. In general terms, you have the option to add an existing content entry, or create & save a new one. Depending on reference type and configuration, you may have to select a model before you can select an entry, and then you will be able to select one or more content entries to relate to the parent entry. Here is a detailed explanation on how to work with each reference type: ### One-to-one references Click `Add existing ` to pick a content entry from an allowed model configured in the schema. Select an entry, then return to the content editor, where it will appear as a reference. Click `+ Create new ` - called `+ Create & replace ` if you have already selected an existing entry - to create, save and relate a new content entry. After saving, it will appear as a reference in the content editor. The model available for selection is preconfigured in the **Schema**. Check out our [References](/docs/developer-guides/schema/references) document to learn more about reference configuration. ### One-to-many references Use the `Add existing ` button to select one or more content entries from a preconfigured model. Simply click on the checkboxes of the entries you want to reference in order to select them, then click on `Add selected `. You will then return to the current content creation screen, and will find the selected entries added as references. Click `+ Create new ` to create, save and relate a new content entry. After saving, it will appear as a reference in the content editor. The model available for selection is preconfigured in the **Schema**. Check out our [References](/docs/developer-guides/schema/references) document to learn more about reference configuration. ### Many-to-many references Use the `Add existing entries` button to select from the preconfigured models available in the schema. Click on a model name to select entries from that model. Use the checkboxes of the entries you want to reference to select them, then click on `Add selected entries`. When you return to the content editor, they will appear as references. You can optionally click on `Add existing entries` again to add more entries from the same model or from a different one. You can also use the `+ Create new entry` to create, save and relate a new content entry. After saving, it will appear as a reference in the content editor. The models available for selection are preconfigured in the **Schema**. Check out our [References](/docs/developer-guides/schema/references) document to learn more about reference configuration. ### Many-to-one references Use the `Add existing entries` button to select from the preconfigured models available in the schema. Click on a model name to select an entry from that model, and use the reference icon to select & add one. When you return to the content editor, they will appear as references. Click `+ Create new entry` - called `+ Create & replace entry` if you have already selected an existing entry - to create, save and relate a new content entry. After saving, it will appear as a reference in the content editor. The models available for selection are preconfigured in the **Schema**. Check out our [References](/docs/developer-guides/schema/references) document to learn more about reference configuration. ## Conditional references If the reference field has `Conditional visibility` configured, it will only display in the content form when the condition is met. You can find out if a reference field has a condition configured by looking at the `Conditional visibility` checkbox in the `Configure` section of your reference configuration screen: ![Conditional references](/images/docs/user-guides/conditional-reference.png) [Check out this document to learn how conditional visibility works.](/docs/developer-guides/schema/conditional-fields) ## Related documents - [References:](/docs/developer-guides/schema/references) This document explains how to configure the different types of references in the schema builder. - [Components or references?:](/docs/developer-guides/schema/components-or-references) This document explains how to choose between a reference or a component depending on your content. - [Reference field type:](/docs/api-reference/schema/field-types#reference) API Reference document on reference configuration. Use this document to configure references through the API. --- # Versioning content Source: https://hygraph.com/docs/developer-guides/content/versioning-content ## Overview Content versioning lets you track changes in published content over time. When versioning is enabled, a new version is created with each published change. The number of saved versions depends on your Hygraph plan. New versions are created when you publish content to any stage. `DRAFT` content is not versioned, and its revision history is not available. Version history is only retained for content published to other stages. - Versioning is available on paid plans only. - Versions do not store assets or referenced entries. In those cases, only the link between a content version and its assets or related entries is preserved. This means that if you delete them, you won't be able to restore them using version comparison. ## How versions are created Versions are automatically generated whenever a content entry is edited and published. It ensures that each published change is saved as a new version. You can find the list of versions in the **Versions** section of the right sidebar. ![Versions section on the sidebar panel](/images/docs/user-guides/content-versions-panel.png) [Learn more about Publishing content](/docs/developer-guides/content/publishing-content). ## Version comparison & restoration Users can compare versions across [content stages](/docs/developer-guides/content/content-stages) to track changes over time. If editors need to revert updates from the `PUBLISHED` stage back to `DRAFT`, they can review both versions side by side and select specific fields to restore. It helps content teams avoid duplicate work when rolling back changes. ![Version comparison - Comparison view](/images/docs/user-guides/version-comparison-view.png) To compare and restore versions: 1. Open a content entry to compare versions. 2. Find the `Versions` section on the right contextual sidebar and click on the version you want to compare the currently published version to. 3. A split-pane window shows both versions side by side: the published version on the left and the draft on the right. The content is separated into the individual fields configured for the model in your schema. The right panel shows an `Edited` pill next to the fields that are different from the draft version for easy identification. To restore a field, hover over it and click `Restore`. To restore all changes, click `Restore all changes` at the top-right of the screen. 4. Click `Apply changes` to go back to the content form, where the entries you selected show the restored content. When you click `Save & Publish`, the system will generate a new version including the latest changes. --- # Hygraph code examples Source: https://hygraph.com/docs/developer-guides/hygraph-code-examples Here is a list of our most recently updated code examples: | Example name | Description | | ------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Algolia | This example demonstrates how to use Hygraph webhooks to sync data to Algolia on new published content. | | Apollo Client 3 | This example demonstrates how to query from Hygraph with `graphql-request` in `React.js`. | | Apollo Server | This example demonstrates how to use `apollo-datasource-graphql` to pull a Hygraph schema into an existing Apollo Server. | | Astro | This example demonstrates how to use the Astro with Hygraph. | | Asset upload example | This example demonstrates how to programmatically import assets from a URL using the Asset Upload endpoint. Uses Bee-Queue to process jobs (recommended for large data imports). | | Eleventy | This example demonstrates how to use the Eleventy and GraphQL Request with Hygraph. | | Express | A simple Express app using `ejs` templating and `awesome-graphql-client`. | | GenQL | This example demonstrates how to query from Hygraph with GenQL. | | Go Lang | This example demonstrates how to query from Hygraph with Go. | | graphcms-image | This example demonstrates how to use @graphcms/react-image with Gatsby. | | GraphQL Codegen | This example demonstrates how to query from Hygraph with Apollo Client 3, TypeScript, and generated types for your Hygraph project with GraphQL Code Generator. | | graphql-mesh | This example demonstrates how to use GraphQL mesh to serve 3 separate APIs, Hygraph, CartQL, and SpaceXLand. | | Gridsome | This example demonstrates how to query data from Hygraph with `@gridsome/source-graphql`. | | Houdini | This example shows how to use the SvelteKit GraphQL library Houdini with Hygraph. | | KitQL | This example shows how to use the KitQL with Hygraph. | | Management SDK | This example demonstrates how to programmatically build a project schema using the Hygraph Management SDK. | | Mutations | This example demonstrates how to mutate Hygraph data with `Next.js.` | | Next Auth | This example demonstrates how to query from Hygraph `NextAuth.js`. | | Next App Router | This example demonstrates how to query from Hygraph with `graphql-request` in `Next.js`. | | Next i18n Routing | This example demonstrates how to use `Next.js` Internationalized Routing with localized Hygraph content. | | Next Image | This example demonstrates how to use the `Next.js` Image Component with your Hygraph assets. | | Next Image Loader | This example demonstrates how to use a custom loader function with `Next.js` Image Component and Hygraph assets. This allows you to perform asset transformations using the Hygraph CDN, rather than the default `Next.js` loader. | | Next mdx remote | This example demonstrates how to use markdown fields from Hygraph with `MDX` in `Next.js`. | | NextJs | This example demonstrates how to query from Hygraph with `graphql-request` in `Next.js`. | | NuxtJs | This example demonstrates how to query from Hygraph with `Nuxt.js`. | | NuxtJs GraphQL | Hygraph, `NuxtJS` & `nuxt-graphql-client`. | | Pagination | This example demonstrates ways you can paginate Hygraph queries. | | Remote Fields | This example demonstrates how to programmatically build a project schema using the Hygraph Management SDK. Using Remote Fields, we create a PricingPlan model to query for product/price data directly from the Stripe API. | | React query | This example demonstrates how to query from Hygraph with React Query in `React.js`. | | ReactJs | This example demonstrates how to query from Hygraph with `graphql-request` in `React.js`. | | Rich Text renderer | This example demonstrates how you can render Rich Text from Hygraph. | | Slinkity | This example shows how to use the Slinkity and GraphQL Request with Hygraph. | | SvelteKit | This example shows how to use the SvelteKit with Hygraph. It uses `graphql-request`. | | SvelteKit & GraphQL Request | This example shows how to use the SvelteKit and GraphQL Request with Hygraph. | | SvelteKit & URQL | This example shows how to use the SvelteKit with Hygraph and URQL. | | Swift | This demonstrates how to use Hygraph with a Swift application for iOS (iPhone / iPad) or Mac. | | Swift & swift-graphql | This demonstrates how to use Hygraph with a Swift application for iOS (iPhone / iPad) or Mac, using popular library `swift-graphql`. | | Swr | This example uses SWR to fetch, and mutate user todos using Next.js API Routes, backed by GraphQL. | | Terraform | Terraform example. | | Union types | Hygraph supports polymorphic relations, also known as GraphQL union types. This example demonstrates how you can build a product marketing page using a schema consisting of polymorphic relations. The example is built with `Next.js`, `graphql-request` and Tailwind CSS. | | Upload Proxy | This example demonstrates how to proxy uploads from your client application to Hygraph to prevent leaking the Permanent Auth Token needed to upload new assets. | | URQL core | This example demonstrates how to query from Hygraph with core URQL client. | | URQL React | This example demonstrates how to query from Hygraph with React URQL client. | | Using where filter | This example demonstrates ways you can order Hygraph query results by using variables. The example is built with `Next.js`, `graphql-request` and swr. | | Vanilla JS | This example demonstrates how to query data from Hygraph in the browser with just JavaScript. | | Variables | This example demonstrates ways you can order Hygraph query results by using variables. This example is built with `Next.js`, `graphql-request` and swr. | | VueJs | This example demonstrates how to query from Hygraph with `Vue.js.` | --- # Working with Hygraph Source: https://hygraph.com/docs/developer-guides/index Hygraph is the Headless CMS allowing you to build digital experiences the way you envisioned them - with all your backends, frontends, and services, working together in harmony. Hygraph gives you instant GraphQL Content APIs to create, enrich, unify, and deliver your content across platforms. ## Getting started flow You can get started with Hygraph by following the **Getting Started flow** which will walk you through: - [Planning your content flow](/docs/getting-started/plan-content-flow/) - [Designing your content models](/docs/getting-started/design-content-models/) - [Building your content models](/docs/getting-started/build-content-models/) - [Configuring reference fields](/docs/getting-started/configure-reference-fields/) - [Configuring enumerations](/docs/getting-started/configure-enumerations/) - [Building components](/docs/getting-started/build-components/) - [Adding components to your models](/docs/getting-started/add-components-to-models/) - [Adding a Remote Source](/docs/getting-started/add-remote-source/) - [Adding Remote Fields](/docs/getting-started/remote-fields/) - [Adding Top-level Remote Fields](/docs/getting-started/top-level-remote-fields/) - [Creating content](/docs/getting-started/content-creation/) - [Localizing content](/docs/getting-started/content-localization/) - [Additional content features](/docs/getting-started/additional-content-features/) - [Queries](/docs/getting-started/api-playground-queries/) - [Mutations](/docs/getting-started/api-playground-mutations/) - [Connecting your frontend using Next](/docs/getting-started/frontend-creation/) - [Connecting your storefront](/docs/getting-started/connect-storefront/) - [Setting up a preview URL](/docs/getting-started/preview-url-creation/) - [Using webhooks](/docs/getting-started/configure-webhooks/) ## Content modeling At the core of every project is the schema. It's important your schema is carefully structured, and designed modelled around the content you will be storing. We've got a guide on the basics of content modeling, and how Hygraph gives you all the tools to structure your content. [Learn more about content modeling](/docs/getting-started/fundamentals/content-modeling) ## Going beyond the basics Once you've completed our [getting started tutorial](/docs/getting-started/onboarding-overview), you will want to explore our growing list of code examples, tutorials, and more. - [API Reference](/docs/api-reference) - Code Examples - [Tutorials](/blog/tag/build-with-hygraph) ## What is a headless CMS? If you're new to the concept of Headless CMS then you should read our blog article. It'll take less than 5 minutes. ## Section contents This section offers a contents map to help you readily find what you're looking for. ### Project guides | Document | Contents | | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | [Project information](/docs/developer-guides/project/manage-project-info) | Contains information about managing your project's global information. In this document you will find information about your project details, how to clone a project, share it, manage support access, leave a project, or delete it. | | [Environments](/docs/developer-guides/project/manage-project-environments) | Contains information about managing your project's environments. In this document you will find information about creating, switching and deleting environments, as well as promoting an environment to master. | | [Multiple environments](/docs/developer-guides/project/multiple-environments) | Contains information about working with multiple environments, such as the reasons to do this, setting up a development environment, maintaining changes that happened while in development, and promoting an environment to master. | | [Roles and permissions](/docs/developer-guides/project/roles-and-permissions) | Contains information about managing the roles and permissions of your project. In this document you will find information about system and custom roles, adding and removing users to roles, and examples on different setups for your project. | | [Audit logs](/docs/developer-guides/project/audit-logs) | Contains information about working with audit logs, which allow you to see all changes inside your project. In this document you will find information about accessing audit logs, event fields, logged resources, logged actions, and filtering logs. | | [Configuring content stages](/docs/developer-guides/project/configure-content-stages) | Learn about how system content stages work, and how to configure custom ones. | | [Update your password](/docs/developer-guides/project/update-your-password) | Contains information about how to update your password. | | [Update billing](/docs/developer-guides/project/update-billing) | Contains information about how to update your billing information. | | [Clone your project](/docs/developer-guides/project/clone-your-project) | Contains information about how to clone your project. In this document you will also find some additional information on cloning limitations. | | [Share your project](/docs/developer-guides/project/share-your-project) | Contains information about how to share your existing project's master environment as a template with others. | | [Locales](/docs/developer-guides/project/manage-project-locales) | Contains information about managing your project's locales. | | [Team members](/docs/developer-guides/project/manage-team-members) | Contains information about managing team members in a project. | | [Collaborator view](/docs/developer-guides/project/collaborator-view) | Contains information about our Collaborator view feature, which shows you in real time all users who are viewing an entry. | | [Content workflows](/docs/developer-guides/project/content-workflows) | Contains information about our Content Workflows feature, which help teams manage content creation and approval in a clear and structured way. | | [Commenting](/docs/developer-guides/project/commenting) | Contains information about our commenting feature. In this document you will find information about notifications and comments. | ### Schema guides | Document | Contents | | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Command menu](/docs/developer-guides/schema/command-menu) | Contains information about using the command menu feature for keyboard navigation. | | [Components](/docs/developer-guides/schema/components) | Contains information about components, which are a predefined set of fields that can be reused across models and content entries. In this document you will find information on how to create and add components to a field, how to use components in content entries, how to query components, how to create and use nested components, and how to decide between components and relations. | | [References](/docs/developer-guides/schema/references) | Contains information about the configuration of reference fields, and the different types available. | | [Components or references](/docs/developer-guides/schema/components-or-references) | Contains information that can help you decide whether to use a component or a reference in your project schema. | | [Enumerations](/docs/developer-guides/schema/using-enumerations) | Contains information about working with enumerations, which can be used to group values within a type. In this document you will find information on creating, adding, deleting, and using enumerations. | | [Taxonomies](/docs/developer-guides/schema/taxonomies) | Contains information about working with taxonomies, which can be used to define hierarchical structures for content classification. In this document you will find information on creating, adding, deleting, and using taxonomies. | | [Live preview](/docs/developer-guides/schema/live-preview) | Contains information about working with the live preview feature to preview content in your frontend before it's published. | | [Remote sources](/docs/developer-guides/remote-data/remote-sources) | Contains information about Remote Sources, which is our content federation utility. With Remote sources, it's possible to add content from other systems and sources to the Hygraph API without having to migrate the content itself. | | [Remote content & data](/docs/developer-guides/remote-data/remote-content) | Contains information about using remote fields to fetch external data. Use this document to learn about remote fields in the context of a regular model, and top level remote fields, which fetch data unrelated to Hygraph. | | [Reserved terms](/docs/developer-guides/schema/reserved-terms) | Contains a list of reserved terms. Attempting to use these terms will result in a warning, informing you that you must use a different word instead. | | [Slug field](/docs/developer-guides/schema/slug-field) | Contains information about working with the slug field. In this document you will find information on how to configure the slug field to be generated from other field values in your model. | | [Conditional fields](/docs/developer-guides/schema/conditional-fields) | Contains information about working with conditional fields. In this document you will find information on how to configure conditional visibility for the fields in your schema. | ### Content guides | Document | Contents | | ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Accessing content](/docs/developer-guides/content/access-created-content) | Contains information about accessing created content. | | [Configuring columns](/docs/developer-guides/content/configuring-columns) | Contains information about configuring columns in order to declutter your content views by hiding information that is not relevant to you. | | [Searching for content](/docs/developer-guides/content/searching-for-content) | Contains information about using the search feature in our content. | | [Using filters](/docs/developer-guides/content/using-filters) | Contains information about using filters in order to restrict the content shown by specific parameters. | | [Content views](/docs/developer-guides/content/content-views) | Contains information about default and custom content views. In this document you will find information on how to update default views, how to create/edit/delete/organise custom views, and how to create/edit/delete view groups. | | [Creating content](/docs/developer-guides/content/creating-content) | Contains information about creating content as well as connecting related content. | | [Updating content](/docs/developer-guides/content/updating-content) | Contains information about updating content as well as sorting related content. | | [Deleting content](/docs/developer-guides/content/deleting-content) | Contains information about deleting content as well as removing related content. | | [Duplicating content](/docs/developer-guides/content/duplicating-content) | Contains information about duplicating content and relations. | | [Publishing content](/docs/developer-guides/content/publishing-content) | Contains information about publishing content. You will also find information on outdated content and on editing published content. | | [Content stages](/docs/developer-guides/content/content-stages) | Contains information about Hygraph's default content stages and about creating custom workflows using content stages. | | [Versioning content](/docs/developer-guides/content/versioning-content) | Contains information about versioning content, which allows you to view the evolution of the project's published content. | | [Content localization](/docs/developer-guides/content/content-localization) | Contains information about content localization, which is supported as a core feature in Hygraph. In this document you will find information on adding translations and showing/hiding fields. | | [Scheduled publishing](/docs/developer-guides/content/scheduled-publishing) | Contains information about scheduling content to be published or unpublished. In this document you will find information on how to schedule items or releases, how to create a release, and scheduling limitations. | | [Customize sidebar](/docs/developer-guides/content/customize-sidebar) | Contains information about customizing your sidebar by adding, removing or reordering widgets. | | [Rich Text editor](/docs/developer-guides/content/rich-text-editor) | Contains information about Hygraph's Rich Text editor, which provides great flexibility when it comes to creating, editing, and manipulating content entries showcasing text and images. | ### Asset guides | Document | Contents | | -------------------------------------------------------- | -------------------------------------------------------------------------------------------- | | [Add asset entry](/docs/developer-guides/assets/add-asset-entry) | Contains information about adding asset entries, which allows adding metadata to your asset. | | [Upload asset](/docs/developer-guides/assets/upload-asset) | Contains step by step information about the asset upload process. | | [Work with assets](/docs/developer-guides/assets/work-with-assets) | Contains information about working with assets. | ### Webhooks | Document | Contents | | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | [Overview](/docs/developer-guides/webhooks/webhooks-overview) | Contains general information about the use of webhooks with Hygraph, such as configuring webhooks, viewing logs, and editing or deleting webhooks. | | [Deploy your Vercel project](/docs/developer-guides/webhooks/trigger-static-build) | Contains all the necessary information to deploy your Vercel project using webhooks. | | [Deploy your Gatsby project](/docs/developer-guides/webhooks/deploy-gatsby-project) | Contains all the necessary information to deploy your Gatsby project using webhooks. |     --- # Audit logs Source: https://hygraph.com/docs/developer-guides/project/audit-logs Audit Logs allow you to see all changes inside of your project. They contain both changes in the schema and content. Audit Logs are an Enterprise pricing feature. Whenever a member of your project creates a new model, updates a content entry, or deletes a webhook, the Audit Logs will retain these actions. ![Audit Logs Explorer](/images/docs/user-guides/audit-logs-view.png) ## Accessing audit logs On eligible projects, Audit Logs are visible for the Owner & Admin role. You can also grant access to Audit Logs by creating a custom role with the "Can view Audit Logs" permission. Clicking on a specific row in the Audit Logs table opens the detail view for that event, including the payload that shows all changes that happened on this event. ![Audit Logs Details View](/images/docs/user-guides/audit-logs-detail.png) If you need to fetch the most recent updates once you are already examining logs, use the refresh button at the top-right of the screen. ![Reload audit logs](/images/docs/user-guides/audit-logs-reload.png) ## Event fields Each audit event will have the following fields present: | Field | Description | | -------------------- | -------------------------------------------------------------------------------- | | Entity ID | The ID of the Resource, e.g. Model ID or Content Entry ID. | | Timestamp | The time at which the change occurred. | | Type (Resource Type) | The type of resource that has been altered. | | Action | The action that was performed on the resource. | | Environment | The environment in which the change occurred. | | Payload | The full payload of the change. We won't include the payload for content changes. | | TriggeredBy | The Member email or Token that triggered the change. | | TriggerType | Can be `USER`, `PAT`, `OPEN` or `THIRD_PARTY`. | ## Logged resources Here's a list of resource types the Audit Logs will keep track of: - Model - Environment - Field - Content - Webhook - Locale - Project - Stage - Member - Role - View Group - Content View - PAT (Permanent Access Token) - Enumeration ## Logged actions These are the actions that can be performed on resources: - Accept (users) - Create - Update - Delete - Publish (content only) - Unpublish (content only) ## Filtering logs You can filter the logs by the [event fields](#event-fields). ![Filtering logs](/images/docs/user-guides/audit-logs-filter.png) ### Filter by action To filter by action, select `Action` from the `Filter` menu: ![Filter by action](/images/docs/user-guides/audit-logs-filter-action.png) Select one of the actions from the dropdown menu to filter the results on the table. ### Filter by entity ID To filter by entity ID, select `Entity ID` from the `Filter` menu: ![Filter by action](/images/docs/user-guides/audit-logs-filter-entityid.png) Enter a resource entity ID in the input field to filter the results on the table. ### Filter by environment To filter by environment, select `Environment` from the `Filter` menu: ![Filter by action](/images/docs/user-guides/audit-logs-filter-env.png) Enter the name of one of your environments in the input field to filter the results on the table. Global actions such as inviting a member to your project count for all environments and do not display with this filter. ### Filter by timestamp To filter by timestamp, select `Timestamp` from the `Filter` menu: ![Filter by action](/images/docs/user-guides/audit-logs-filter-timestamp.png) Select a condition from the dropdown and then enter a timestamp value in UTC format in the input field to filter the results on the table. ### Filter by user If you want to search for the audit logs of a specific team member, select `Triggered by` from the `Filter`: ![Search for logs of a team member](/images/docs/user-guides/audit-logs-member.png) Leave the trigger type as `User` and write their email in the `Triggered by` input field. The table will be ordered descending by default, so the newest entry is shown first. ### Filter by trigger type To filter by trigger type, select `Trigger type` from the `Filter` menu: ![Filter by action](/images/docs/user-guides/audit-logs-filter-triggertype.png) Select a trigger type from the dropdown menu to filter the results on the table. - **App Token:** Actions triggered by an application token used for API authentication or automated processes. - **Open:** Requests sent to a public content API, without any auth tokens. **We do not recommend enabling permissions to mutate content in these cases**. We suggest that you use a PAT instead. - **PAT:** Actions performed using a Permanent Auth Token (PAT) for API access. - **Third Party:** Events initiated by external integrations or connected third-party applications. - **User:** Actions directly performed by a logged-in user in the Hygraph UI. ### Filter by type To filter by type, select `Type` from the `Filter` menu: ![Filter by action](/images/docs/user-guides/audit-logs-filter-type.png) Select a type from the dropdown menu to filter the results on the table. ### Filter by user email To filter by user email, select `User email` from the `Filter` menu: ![Filter by action](/images/docs/user-guides/audit-logs-filter-email.png) Enter an email in the input field to filter the results on the table. --- # Backup & recovery Source: https://hygraph.com/docs/developer-guides/project/backup-and-recovery ## Overview **This feature is an addon for enterprise customers. [Contact sales to learn more](/contact)** In the case of unintentional deletions, Backup & Recovery allows project admins to roll back their main environment to a previous working version within seconds. ## What you can do - Automatic daily backup of a project's `master` environment, with up to a 7-day retention period. - Easy recovery for project admins in case of accidental deletions. - Recovery within seconds to a previous backup as a new environment that you can promote. - Backups contain content, assets & schema. ## Backup creation When the feature is enabled, backups are created automatically every day, during non-working hours, and kept for up to 7 days. Project admins can see the next scheduled backup date & time, and the general retention. We also show the list of all available backups: ![Clone settings](/images/docs/user-guides/project-guides/backups-list.png) ## Recover backup Click `Recover backup` on one of the backup cards to start the process: ![Recover backup](/images/docs/user-guides/project-guides/backups-recover.png) A modal with two selection options will display: ### Recover backup only ![Recover backup only](/images/docs/user-guides/project-guides/recover-backup-only.png) The `Recover backup only` option creates a new environment from the backups. ### Recover backup & promote to master The `Recover backup & promote to master` option turns the backup into a normal environment and promotes that to master. The backup replaces the old master environment. ![Recover backup & promote to master](/images/docs/user-guides/project-guides/recover-backup-and-promote.png) If you select this option, you must also choose a name for the demoted master environment, [like in our normal promotion flow](/docs/developer-guides/project/manage-project-environments#promote-environments-to-master). This process will only take a few seconds. #### Asset URLs change Since backups are created as a new environment, the asset URLs will be different. Make sure to check for asset URLs stored in other places, such as source code or external systems. Please note that, while in the past every single handle would have been updated, with the new asset system only the base URL changes on cloned environments. Original asset URL: `https://media.graphassets.com/6gB5FZ9mQiWHOu6Lt95p` Asset URL after backup: `https://media.graphassets.com/` With the new **Hygraph Asset Management System**, the following asset URL: ``` https://eu-west-2.graphassets.com/cm1q9b9c401cv07ut4wcgf2uj/cm2kmqsf6002a07ukg3z7sjzg ``` Would turn into something like this: ``` https://eu-west-2.graphassets.com//cm2kmqsf6002a07ukg3z7sjzg ``` Since the asset ID remains the same, you can simply run a search for the old asset environment ID and replace it with the new one. --- # Clone your project Source: https://hygraph.com/docs/developer-guides/project/clone-your-project ## Overview You can clone your existing project's master environment. Inside your project settings you can click on `Clone project`. ![Project settings](/images/docs/user-guides/project-settings-clone.png) Give your project a name, optional description, and select the region where its data will be stored, whether you want to clone existing content, assets, and/or configured webhooks. ![Clone settings](/images/docs/user-guides/clone-settings.png) ## Cloning limitations ### Cloning in progress If one of your projects is in the process of being cloned, you will not be able to start another clone until the previous process is finished. Attempting to do so will prompt a warning message indicating this at the top of the screen. ![Cloning limitation warning message](/images/docs/user-guides/cloning-limitation-warning.png) Users can process one cloning operation at a time, irrespective of the project. Even if a clone has been scheduled, users can continue issuing other schema changes - except cloning - in their projects and environments. These limitations also affect public cloning. ### Clone project access Only project **Owners** can access the cloning controls and links. Users other than the project owner cannot see the **Clone project** screen section in `Project Settings > General > Project`. ### Apps When you clone an environment, the apps installed in it are cloned too. However, cloning creates apps in **"incomplete setup"** for security reasons. You will need to complete your app configuration by manually adding passwords, or API Keys. After completing setup, the app will work as it did in the previous environment. ## Public cloning If public cloning is enabled under [Share your project](/docs/developer-guides/project/share-your-project) in `Project settings > General > Project`, you get a link that you can share. ![Project settings - Public cloning](/images/docs/user-guides/project-settings-share.png) Using this link, other users will be able to clone your project into their own accounts. This is the screen they will see when using the public cloning link: ![Project settings - Public cloning](/images/docs/user-guides/public-cloning-screen.png) From here on, they can follow the [Create a project](/docs/getting-started/fundamentals/quickstart#create-a-project) flow. You can use this to create your own starter project and share it with other people. ## Create your own starter If your work involves using a large number of very similar projects, public cloning might help you work more efficiently. In the following video, Bryan urges you to **work smarter not harder** by creating a project to use as a starter, then using cloning to create copies of it, which you can then work on: --- # Collaborator view Source: https://hygraph.com/docs/developer-guides/project/collaborator-view Our Collaborator view feature helps editors avoid accidentally overwriting and losing content that someone else is currently working on. The feature shows you exactly who's interacting with a content entry at the same time as you. ## How it works You will see your own avatar at the top-right corner of the screen as soon as you open a content entry: ![Collaborator view - User's name on hover](/images/docs/user-guides/collaborator-view/collaborator-view-hover-text-you.png) If someone else opens that content entry, you will see their avatar next to yours. You can hover over an avatar to see the name of the user: ![Collaborator view - User's name on hover](/images/docs/user-guides/collaborator-view/collaborator-view-hover-text.png) If more than 3 users are looking at the content entry, the screen will only display two, plus a counter that shows how many more are viewing it. On hover, the counter will display a card showing the avatars and names of all users currently viewing the content entry: ![Collaborator view - User's name on hover](/images/docs/user-guides/collaborator-view/collaborator-view-hovercard.png) --- # Commenting Source: https://hygraph.com/docs/developer-guides/project/commenting Commenting is a functionality designed with larger teams containing multiple content editors in mind, to promote work collaboration. You will be able to leave comments in content entries, as well as receive notifications related to the comments. ## Notifications The **Notifications** menu is located in the sidebar of your **Hygraph** project, by the bell icon. If you have any unread notifications, a red alert will display next to it. You will be notified whenever you are mentioned in a comment, and when a comment that you have either authored or have been mentioned in is resolved or re-opened. To access these notifications, simply click on the **Notifications** menu. For each one, you have the following options: - `Mark as read` or `Mark as unread` depending on the notification status. - Click on the notification to access the related comment. - `Mark all as read` by clicking on the button at the top-right corner of the notifications screen. - Filter notifications by `All` or `Unread`, by clicking the filter icon at the top of the screen. - Use the configuration menu at the top of the screen to select how you want to receive notifications: in the app, via email, none, or both. Please note that notifications displayed here are from all your projects, not just the project you're currently in. ## Comments You can find the comments on the sidebar of a content entry, by clicking on the Comments button at the top. ![Comments](/images/docs/editor-guides/collaboration/comments-sidebar.png) At the top, the sidebar displays how many comments there are in the current content entry, along with a status filter to help you quickly find what you're looking for. ![Comments number & status filter](/images/docs/editor-guides/collaboration/comments-number-status-filter.png) When you click on a notification, it will take you to the corresponding content entry, where the comment you were notified about will be highlighted. In the **Comments** sidebar, you can: - **Leave a new comment:** Write your comment in the text box located at the top of the **Comments** sidebar and click `Add`. You can mention people as well as project roles in your comment by using `@`, which will display a list of users and roles for you to select from. Mentioning people or roles may cause an alert to pop up in their [notifications](/docs/developer-guides/project/commenting#notifications), and/or receive an email depending on their notifications configuration. - **Assign a comment:** When you're writing a new comment, you can click `Assign` to assign it to a team member in the project. Upon clicking, a list of users will display and you can simply select one with a click. - **Use the status filter:** This dropdown menu allows you to sort comments by `All`, `Assigned`,`Open`, and `Resolved`. - **Reply to a comment:** If you want to reply to a specific comment, you can click **Reply** under it. Once saved, the comment will display as nested within the parent comment. You can click `Reply` to show it, or `Hide` to hide it. - **Use the context menu:** Use this menu to access the following actions. - **Edit:** Clicking on this option allows you to edit the contents of a comment, then click `Update` to save it. You can only edit your own comments while they are open. This option only displays for your own comments. - **Resolve / Re-open:** Clicking on this option marks the comment as resolved / re-opened. The people mentioned in the comment will be [notified](/docs/developer-guides/project/commenting#notifications). - **Un-assign:** Clicking on this option un-assigns a comment. - **Copy link**: Clicking on this option copies the link to the comment. When used, that link will lead to the comment, which will be highlighted in order to be easily found. - **Delete:** Clicking on this option deletes the comment. This option only displays for your own comments. The Comments sidebar will only appear in a content entry once it has already been saved for the first time. Before the first save, only the Info sidebar will be available. ## Commenting & notifications workflow The following diagram shows a simplified workflow including 2 users collaborating through our commenting feature: ![Hygraph commenting & notifications workflow](/images/docs/user-guides/workflow-commenting.png) --- # Configuring content stages Source: https://hygraph.com/docs/developer-guides/project/configure-content-stages ## Overview Hygraph offers two OOTB system content stages, plus the ability to [create custom content stages](/docs/developer-guides/project/configure-content-stages#creating-custom-content-stages) depending on your plan. This document provides information on how system content stages work, and how to work with custom stages. [Check out our API Reference document on content stages](/docs/api-reference/content-api/content-stages) ## What you can do You can use the [system content stages](/docs/developer-guides/project/configure-content-stages#system-content-stages) OOTB, or customize your workflow by creating [custom content stages](/docs/developer-guides/project/configure-content-stages#custom-content-stages). ## System content stages Hygraph has two system content stages, `DRAFT` and `PUBLISHED`. These content stages cannot be edited or deleted. `DRAFT` allows people to add and edit content without affecting published content. Promoting the content to the `PUBLISHED` stage, pushes it live, which means it can be consumed by others. Click [here](/docs/developer-guides/content/content-stages) learn more about how system content stages work. ## Custom content stages ### Creating custom content stages To create your own content stages, navigate to **Project settings > General > Content stages**. The default screen shows the `DRAFT` and `PUBLISHED` system stages, and offers the option to upgrade your plan if you want to configure any additional stages. ![Content Stages Project Settings](/images/docs/user-guides/content-stages-settings.png) The `QA` stage card is grayed out as it's not part of the **Community Plan**. Clicking on the `Upgrade to add this content stage` link at the bottom of this card displays a popup where you can select the best plan for your project. ![QA Stage](/images/docs/user-guides/content-stages-qa.png) You can also change the order of your destination content stages by clicking on the six dots handle, located to the left of each configured stage card, and dragging. If you haven't reached the limit of stages for your plan, then you will simply be provided with the option of adding a new stage. ![+ Add stage](/images/docs/user-guides/content-stages-add.png) If your project allows custom content stages, clicking on `+ Add stage` displays a screen that requires you to provide a `Display name` and select a label color. You can optionally add a description. The `API ID` field will be autocompleted as you write the display name. ![+ Add stage](/images/docs/user-guides/content-stages-create.png) After typing in all the necessary information, you can click `Add` to create the content stage. Creating custom content stages is available to paid plans. Upgrade your plan. ### Editing custom content stages To edit a custom content stage, navigate to **Project settings > General > Content stages** in your Hygraph project, and click on the `Edit` button on the card of the content stage you want to edit. ![Edit stage](/images/docs/user-guides/content-stages-edit.png) The content stage details page will display. Edit the information you need to change, then click `Update` to save your changes. ![Edit stage - details screen](/images/docs/user-guides/content-stages-edit-details.png) ### Deleting custom content stages To delete a custom content stage, navigate to **Project settings > General > Content stages** in your Hygraph project, and click `Delete` on the card of the content stage you want to delete. ![Delete stage](/images/docs/user-guides/content-stages-delete.png) A confirmation screen displays, where you need to type in the stage name, then click `Delete stage` to proceed. ![Delete stage - confirmation screen](/images/docs/user-guides/content-stages-delete-confirmation.png) --- # Content Freeze Source: https://hygraph.com/docs/developer-guides/project/content-freeze If you change a field or model while an editor is mid-save, their entry can conflict with the new schema, or publish in a state you didn't intend. Editors can also lose work if you promote schema changes between environments while they're still working. Content Freeze pauses content and asset creation, editing, and publishing so you can promote schema safely. ## Availability - **Permission**: Only users with the **Allows starting, scheduling, and lifting content freezes** permission can trigger, manage, or lift a freeze. The Admin role includes this permission by default. - **Plan**: Available on every plan. Content Freeze only blocks Save and Publish actions in Studio. It does not enforce anything at the Content API level. Scripts, integrations, or direct API calls can still read and write content while a freeze is active. ## Configure a freeze 1. Navigate to **Project Settings > Governance > Content Freeze**. 2. Under **Start or schedule a freeze**, choose a **Scope**: **Specific environments** or **Entire project (includes future environments)**. 3. If you chose **Specific environments**, select one or more from the **Environments** dropdown. 4. Set **Start**: **Now** or **At a scheduled time**. 5. Set **End**: **Until I lift it** or **At a scheduled time**. 6. (Optional) Add a **Message**. This message is shown to all users while the freeze is active. 7. Click **Apply freeze**. ![Content Freeze configuration form](/images/docs/developer-guides/project-guides/content-freeze.png) Freezes you create appear under **Active & scheduled**, listing the scope, the time window, and who set it. ![Content Freeze active & scheduled list](/images/docs/developer-guides/project-guides/content-freeze-active-scheduled.png) While a freeze is active or scheduled in their environment, all users see a banner on every page in Studio. Once the freeze is **active**, users can't add new entries or assets, or click **Save** or **Publish** on content entries or assets. A scheduled freeze shows the banner before it starts, but editing remains available until the start time. Reading and browsing content still works through a freeze. ## Lift a freeze early 1. Go to **Project Settings > Governance > Content Freeze**, or click **Manage freeze** in the Studio banner. 2. Find the freeze under **Active & scheduled**. 3. Click the unlock icon in that row to lift it. ![Content Freeze lift form](/images/docs/developer-guides/project-guides/content-freeze-lift.png) Users regain the ability to save and publish as soon as you lift the freeze. ## Review past freezes Completed freezes, whether lifted early or ended on schedule, appear under **Recent freezes** on the same page. Each row shows the scope, the time window, who set the freeze, and who lifted it. ![Content Freeze recent freezes list](/images/docs/developer-guides/project-guides/content-freeze-recent-logs.png) ## Related docs - [Content Freeze API reference](/docs/api-reference/basics/content-freeze): `setContentFreeze` and `liftContentFreeze` mutations, and the `contentFreezes` query. - [Promote environments](/docs/api-reference/basics/environments#promoting-environments): Move schema changes between environments. --- # Content workflows Source: https://hygraph.com/docs/developer-guides/project/content-workflows ## Overview **Content Workflows** help teams manage content creation and approval in a clear and structured way. ### Why content workflows matter Publishing content often involves multiple steps, like reviews, QA, translations, and asset creation. Without a workflow, teams face challenges such as: - **Content consistency:** Changes can be made after review, leading to mistakes. - **Transparency:** It's hard to track content status and know who needs to take action. - **Workflow enforcement:** Content might get published before it's fully approved. Hygraph Content Workflows solve these problems by creating a structured, step-by-step process. This ensures teams follow a clear path from draft to publication. ### Key features & benefits of workflows Hygraph Workflows help teams manage content efficiently by providing: - **Structured workflows:** Admins can set up step-by-step workflows with clear stages. - **Granular permissions:** Each step can have assigned roles, ensuring only the right people can edit or approve content. - **Automated publishing:** Content can move to the next stage or be published automatically after approval. - **Better visibility and tracking:** Editors can filter and track content based on its workflow stage. - **Stronger compliance and control:** Prevents unauthorized changes and keeps content versions consistent. ## Create a workflow 1. Navigate to `Project Settings > Governance > Workflows`. 2. Click `+ Add Workflow` to create a new one. 3. Provide a name and a description, then click `Add workflow` to continue. 4. Newly created workflows will contain default start & end steps. You can edit the default steps by clicking `Edit step`, or move them to a different place in the workflow using the arrows that display on hover or the handlebars. If your project has no workflows configured, you will see the following screen: ![Workflows screen - empty state](/images/docs/user-guides/workflows-empty-state.png) ## Initial setup Once the workflow is created, you land on the setup screen: ![Content workflows initial setup](/images/docs/user-guides/workflows-setup.png) Select initial setup options: | Field | Description | | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Model` | Optional. Use the dropdown to select which models the workflow applies to. | | `Workflow override` | Optional. Use the dropdown to select roles that can choose to ignore the rules of the workflow to perform actions on content entries, in cases where changes are needed urgently. | Click `Update configuration` to save your selection. Next, you must configure the workflow steps. ## Add a workflow step All workflows have two steps by default, the `Start` & `End` steps. This is the minimum number of steps a workflow can have, as each workflow must have a beginning and an end. You can add any number of steps in between them. To add a new step, click `Add step`: ![Add a workflow step](/images/docs/user-guides/workflows-add-step.png) ## Step configuration You will see the step configuration panel every time you add a new step or edit an existing one. ![Content workflows step configuration](/images/docs/user-guides/workflows-step-config.png) | Field name | Description | | ------------------ | ------------------ | | `Step name` | Enter a name for the step. | | `Step color` | Use the dropdown to select a color to identify the step. | | `Description` | Optional. Write a description for the step. | | `Access` | Select one or more roles that can move the step to the next or the previous one and who will be granted the edit permissions you configure next. | | `PAT Access` | Optional. Select the Permanent Auth Tokens (PATs) allowed to perform actions on entries in this step. This option is available only if your project includes a Permanent Auth Token and applies to all workflow steps except AI agent steps. Existing PAT permissions continue to apply, so you need to make sure the selected token has the necessary permissions to run this step.| | `Edit permissions` | Use the radio buttons to choose whether the users with step access can edit entries or not. | | `Return to` | Optional. Use the dropdown to select a step that entries can return to if necessary. You can only select a previous step. | | `Publish to` | Optional. Select one or more content stages to automatically publish content entries to when they move to this step. | After configuring the step, click `Add step` for new steps, or `Update step` if you were editing an existing one. Any [AI agents](/docs/hygraph-ai/ai-agents) configured in the workflow appears automatically as a workflow step. ### Publishing & unpublishing actions In Workflows, **publishing actions** are automated. Content entries in the workflow cannot be published on demand; they are automatically published when they move to a specific step with a `Publish to` stage configured. **Unpublishing actions** are allowed from any stage at any point, but republishing after that requires sending the entry back to the previous step to restart it. Unpublishing actions are not the same as moving a content entry back to a previous workflow step. When you return a content entry to a previous step in the workflow, the content entry is not unpublished, and is still accessible online. ## Moving workflow steps When you hover over a workflow step card, the following options display: ![Moving content workflow steps](/images/docs/user-guides/workflows-move-step.png) Click on the arrow icons to move a step up or down in the workflow. Consider that workflow steps can have `Return to` steps configured. Editing the position of a step in the workflow to a step before its `Return to` step, will break this logic and clear the configured return field. In the following example, moving the step `Middle 2` before `Middle 1` (its configured `Return to` step), will clear the return field configuration: You can also use the six-dots handlebars to drag and drop a workflow step to move it. ## Edit a workflow step To edit a workflow step, click the `Edit` button that displays when you hover over the step card: ![Edit a workflow step](/images/docs/user-guides/workflows-edit-a-step.png) Check out the [Step configuration](#step-configuration) section of this document to see all possible actions on the edit screen. ## Delete a workflow step To delete a workflow step, hover over the step card, click on the contextual menu to display the options in it, and click `Delete step`: ![Delete a workflow step](/images/docs/user-guides/workflows-delete-a-step.png) You will need to confirm this action by clicking `Delete step` again on the confirmation popup. This action cannot be rolled back. ## Enable or disable a workflow To enable a workflow, select the `Enable workflow` toggle that you can find at the top-right corner of the **Edit workflow** screen, or on the workflows table in `Project settings > Governance > Workflows`. When the workflow is enabled, its rules apply to all entries in the configured models. To disable an enabled workflow, click on the toggle as well. When a workflow is enabled, the toggle reads `Disable workflow`. ## Edit a workflow To edit a workflow, go to `Project settings > Governance > Workflows` and click on the workflow hat you want to edit on the table: The **Edit workflow** screen will display, and you can change its configuration. ## Delete a workflow To delete a workflow, go to `Project settings > Governance > Workflows`, click on the context menu, and select `Delete Workflow`: You will need to confirm this action by clicking `Delete Workflow` again on the confirmation popup. This action cannot be rolled back. ## Workflows in content entries When you open a content entry, the system checks if it is assigned to a workflow, and if editing is allowed. **If editing is not allowed**, you will access a read-only version of the content entry. You can read the entry and leave comments, but you cannot modify the contents. You can also move it to the next step in the workflow if you have this permission. **If editing allowed**, you can work on it normally. On the sidebar, you can see what workflow step the entry is on: ![Workflows in content entries](/images/docs/user-guides/workflows_sidebar_widget.png) Even if the user does not have access & the Workflows sidebar widget is disabled, it will show which roles can work on the content entry. If a workflow step is assigned to an [AI agent](/docs/hygraph-ai/ai-agents), the agent automatically starts processing as soon as an entry moves into that step. This ensures tasks run seamlessly within the defined workflow. ### Move the entry to a different step If you have permission to move the entry to the next step in the workflow, the `Move to` button at the top-right of the screen will be enabled. You can click `Move to`, to move the entry to the next step in the workflow, or click the arrow icon to display the workflow actions: ![Content workflows - entry actions](/images/docs/user-guides/workflows_entry_actions.png) | Workflow action | Description | | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Return to` | Select this option to return the content entry to its configured return step. This option is enabled only if the workflow step has a `Return to` step configured. | | `Move forward to` | Select this option to move the content entry to the next step in the workflow. | | `Override workflow for this entry` | Select this option to override the workflow for the entry. When you override the workflow, you can perform any actions, just as if there wasn't a workflow configured. This option is enabled only if you have override permission. | ### Publish a content entry in the workflow If you move a content entry into a step with `Publish to` configured, the entry will automatically be published to the configured stage. You can publish entries to intermediate content stages such as `QA`, or to `PUBLISHED`. When publishing an entry to the `PUBLISHED` stage, you will have the option to publish it right away or to schedule publishing. #### Publish now This action publishes your content to the `PUBLISHED` stage, meaning it will be available online for others to consume. If your content entry contains unpublished assets or references, you will be prompted to publish those too. Moving an entry to the next step & publishing at the same time is an atomic operation. This means both events will succeed or fail. #### Schedule publishing If you select scheduled publishing, the side sheet will open for you to schedule publication of the content entry. When you schedule an entry to publish to a stage, the workflow will move it to that stage but since publishing is scheduled in the future, it will not be published right away. If you open the content entry at this time, it will display as read-only. In order to avoid workflow inconsistencies, the only way to edit this entry is to return it to the previous step, if you have the permission. [Check out our scheduled publishing docs](/docs/developer-guides/content/scheduled-publishing) to learn how to schedule single entries or add them to a release. ## Webhooks for workflows You can trigger a webhook when you move to a new workflow step. While [configuring your webhook](/docs/developer-guides/webhooks/webhooks-overview#configure-webhooks), choose the following settings: - Under General, select the **Include payload** check box. This ensures that the webhook delivers a detailed payload including the workflow step, `workflowStep`. - Under Actions, select **Transition Step**. This ensures that whenever your workflow transitions to a new step, the webhook is triggered. ![Webhooks for workflows](/images/docs/user-guides/content-workflows-transition-step.png) ## Workflows troubleshooting ### I can't edit content entries If your role is configured to edit entries in a model, but you are not able to see this model or edit the content entries, you need to check permissions. Content workflows are an additional layer of control over permissions, but do not override them. --- # Manage your project's environments Source: https://hygraph.com/docs/developer-guides/project/manage-project-environments Environments allow you to safely work on isolated instances of your project. They help you make changes to you schema and test new content structures without breaking your production systems. ![Manage environments](/images/docs/user-guides/project-guides/projectsettings-manage-environments.png) ## Create an environment To create a new environment, we'll use the environment cloning flow: ![Create an environment](/images/docs/user-guides/project-guides/projectsettings-create-environments.png) 1. Navigate to the **Project Settings** tab in the left-hand sidebar of your Hygraph project. 2. Select **Environments** under general settings. 3. Click on the `Clone` button of the Master Environment card. 4. Fill in the form: - `Display name`: Provide a display name for your environment. This is the name that will be displayed in Hygraph. - `Endpoint alias`: This is created automatically as you type in your display name, but can be edited if you wish to. Your endpoint alias is the identifier used in the URLs of the webapp. - `Description`: This is where you can add a description for your new environment. - `Clone with context`: Checkbox that allows you to select whether you'd like to include the content from the master environment. - `Webhooks`: Checkbox that allows you to select whether you'd like to clone the project's webhooks. They will be disabled initially. Users, Roles & Permissions, Permanent Auth Tokens, and billing will not be cloned between projects. ### Asset URLs change Asset URLs will be different after a clone. Make sure to check for asset URLs stored in other places, such as source code or external systems. Please note that, while in the past every single handle would have been updated, with the new asset system only the base URL changes on cloned environments. Original asset URL: `https://media.graphassets.com/6gB5FZ9mQiWHOu6Lt95p` Asset URL after cloning: `https://media.graphassets.com/` With the new **Hygraph Asset Management System**, the following asset URL: ``` https://eu-west-2.graphassets.com/cm1q9b9c401cv07ut4wcgf2uj/cm2kmqsf6002a07ukg3z7sjzg ``` Would turn into something like this: ``` https://eu-west-2.graphassets.com//cm2kmqsf6002a07ukg3z7sjzg ``` Since the asset ID remains the same, you can simply run a search for the old asset environment ID and replace it with the new one. ## Switch environments You must have at least two environments to be able to switch. To switch environments, click the **switch** dropdown in your Hygraph top bar. Select the environment that you want to switch to. ![Switch environments](/images/docs/user-guides/project-guides/projectsettings-switch-environments.png) ## Promote environments to master You must have at least two environments to be able to do this. To promote an environment to master, simply click on the `Promote to master` link in the environment card. ![Promote environment to master](/images/docs/user-guides/project-guides/projectsettings-promote-env-to-master.png) You will be prompted to provide a `New display name` and a `New API ID` for the current master environment being replaced. Fill in this information then click on `Promote to master`. ## Delete environments You must have at least two environments to be able to do this. To delete an environment, simply click on the `Delete` link in the environment card. A warning will display to make sure you did not click on the link by mistake, so you need to confirm your choice by clicking on the `Delete environment` button to finalize the process. ![Delete environment](/images/docs/user-guides/project-guides/projectsettings-delete-environments.png) --- # Manage your project's global information Source: https://hygraph.com/docs/developer-guides/project/manage-project-info Use this screen to edit your project's name and description, clone or share the project, grant Hygraph support temporary access for troubleshooting, leave the project, or delete it. To access it, go to **Project settings > General > Project**. ![Project information](/images/docs/user-guides/project-guides/projectsettings-project-information.png) ## Permissions What you can do on this screen depends on whether you are the project Owner and which permissions your role includes. The **Admin** and **Developer** roles include **Change the name, picture and description of a project** by default. Editor, Contributor, and new custom roles do not, unless you add it. | Action | Who can do it | |---|---| | Edit project details and picture | Users with **Change the name, picture and description of a project** permission | | Enable or disable support access | Users with **Change the name, picture and description of a project** permission | | Clone project | Project Owner only | | Share project | Project Owner only | | Support access | Users with **Change the name, picture and description of a project** permission | | Leave project | Any member who is not the project Owner | | Delete project | Project Owner only | For more information, see [Roles - Project & Studio access](/docs/getting-started/access-and-permissions/management-api-permissions#roles-project-and-studio-access). ## Project details This section shows your project's core identifiers, name, description, and picture. You can't edit **Id** and **Region**. Hygraph sets them when you create the project. You can copy the project ID here. You can edit **Name** and **Description**. To change your project's picture, select **Upload** and choose a new image. ![Project details](/images/docs/user-guides/project-guides/projectsettings-project-details.png) ## Clone project Only the project Owner can clone a project. Cloning creates a full copy of your project, including its schema, content, and settings. Select **Clone project** to start. [This document](/docs/developer-guides/project/clone-your-project) covers the cloning process in detail. ![Clone your project](/images/docs/user-guides/project-guides/projectsettings-clone.png) ## Share project Only the project Owner can enable public cloning. Sharing lets other users clone your project using a public link. Select **Enable public cloning** to turn this on. Anyone with the public clone link can then clone your project. [This document](/docs/developer-guides/project/share-your-project) covers project sharing in detail. ![Share your project](/images/docs/user-guides/project-guides/projectsettings-share.png) ## Grant access to Hygraph support Support access grants the Hygraph support team read-only access to your project so they can help you debug an issue. They can view your project but cannot make any changes while support access is enabled. To enable or disable support access, you need the **Change the name, picture and description of a project** permission. Select **Allow the Hygraph team to access your project** to turn it on. Support access stays on until you turn it off yourself. Hygraph doesn't revoke it automatically. Turn support access off once your issue is resolved. ![Support access](/images/docs/user-guides/project-guides/projectsettings-support-access.png) ## Leave project If you are not the project Owner, you can leave the project instead of deleting it. After you leave a project, you lose access, and the project stays intact for the remaining members. 1. Select **Leave**. 2. Complete the survey explaining why you're leaving. 3. Type your project's name to confirm, then select **Leave**. To rejoin later, a team member must invite you again. ![Leave project](/images/docs/user-guides/project-guides/projectsettings-leave-project.png) ## Delete project Only the project Owner can delete a project. Other members see [Leave project](#leave-project) instead. Deleting your project is permanent and cannot be undone. 1. Select **Delete project**. 2. Complete the survey explaining why you're deleting the project. 3. Type your project's name to confirm, then select **Delete**. ![Delete your project](/images/docs/user-guides/project-guides/projectsettings-delete.png) ## Related docs - [Clone your Hygraph project](/docs/developer-guides/project/clone-your-project): Walk through the full cloning process step by step. - [Learn about sharing your project](/docs/developer-guides/project/share-your-project): Understand what happens when you enable public cloning. - [Roles and permissions](/docs/getting-started/access-and-permissions/user-roles-and-permissions): Confirm which roles can access project settings. --- # Manage your project's locales Source: https://hygraph.com/docs/developer-guides/project/manage-project-locales Locales define the languages available in your Hygraph project. You can add multiple locales and configure schema fields to be localized. Once enabled on a field, content editors can provide values per locale. Read the Localization API reference for details. ## Access project locales Locales are **environment-specific**. If you're working with a project using more than one environment, you must configure locales separately in each environment. To manage locales: 1. Open your project. 2. Navigate to **Project settings > Locales**. 3. Select **Locales**. ![Project locales](/images/docs/user-guides/project-guides/projectsettings-locales.png) ## Add a locale The default locale for new projects is **English**. To add a new locale: 1. In your project settings, select a language from the **Display name** dropdown. 2. Click **+ Add**. The new locale is now available for localized fields in this environment. ## Edit a locale To update an existing locale: 1. In your project settings, click **Edit** next to the locale. 2. Update the configuration as needed. Changes apply immediately to the current environment. ![Edit locales](/images/docs/user-guides/project-guides/projectsettings-locales-edit.png) ## Modify the default locale Each project environment has one default locale. The default locale is used as the primary language fallback for content. [AI Assist](/docs/hygraph-ai/ai-assist) uses the default locale for content generation and content improvement. Changing the default locale affects how AI Assist generates and enhances content. To modify the default locale: 1. Click **Edit** next to your default locale. 2. Update the configuration. Ensure that the display name is a valid locale and it is not the same as any other locale in the project. ## Delete a locale You can delete any additional locale that is not set as default. To delete a locale: 1. Open the context menu next to the locale that you want to remove from your project. - The context menu will only display for additional locales, as it is not possible to delete the default locale. 2. Select **Delete**. 3. Confirm the deletion. A confirmation dialog appears before the locale is permanently removed. ![Delete locales](/images/docs/user-guides/project-guides/projectsettings-locales-delete.png) --- # Work with multiple environments Source: https://hygraph.com/docs/developer-guides/project/multiple-environments Promoting schema changes to your master environment can put production content at risk. This doc covers two workflows for promoting changes safely: applying an isolated migration directly, or staging changes in a third environment before promotion. ## Why work with multiple environments After you launch a Hygraph project into production, you'll continue improving it. Many improvements require schema changes, since your API's data and shape evolve with new requirements. Applying a schema change directly to your master environment affects your production websites or apps immediately, which puts your business at risk. Hygraph supports multiple environments so you can iterate on your schema safely before finalizing a content model. An environment is a full copy of your project. Every project starts with a master environment, and your API endpoint reflects it directly. You can find your master environment API endpoint in **Project settings > Access > API Access > Endpoint > Content API**. Here's the endpoint for a master environment: ![Master environment endpoint example](/images/docs/user-guides/multiple-environments/multiple-environments-master-env-endpoint.png) ## Set up a development environment To create a new environment cloned from your master environment: 1. Go to **Project Settings > Environments**. 2. Select the environment you want to clone from. 3. Click **Clone**. ![Clone master environment button in Environments screen](/images/docs/user-guides/multiple-environments/multiple-environments-clone-master-button.png) 4. Complete the clone form: - **Display name**: Set the environment name. - **Endpoint alias**: Auto-completes from the display name. - **Accent color**: Set the environment's accent color. - **Description**: Add a description for the environment. - **Clone with content**: Clone existing content and assets into the new environment. Recommended if you want to test your development environment with real content. - **Webhooks**: Clone your project's webhooks into the new environment. The webhooks will be initially deactivated in the cloned environment. ![Clone new environment from Master Environment](/images/docs/user-guides/multiple-environments/multiple-environments-clone-from-master.png) 5. Click **Clone** to create the environment. Cloning can take time, depending on your project's size. You now have a clone of your master environment to experiment on without affecting production. Switch between environments using the environment dropdown in the top bar. ![Project environments](/images/docs/user-guides/multiple-environments/multiple-environments-environments.png) ## Content changes made during development Building a new feature takes time, sometimes days, weeks, or months before it ships. During that time, editors keep working in your master environment. Their changes exist only there. Your cloned development environment stays at the state it was cloned from, so promoting it to master would revert content back to that point, undoing everything editors added since. Promoting an environment to master means swapping that environment with master, so it becomes the new master. To promote both your latest schema and your latest content, use one of these workflows: - [Isolate changes and apply them to production](/docs/developer-guides/project/multiple-environments#isolate-changes-and-apply-them-to-production) - [Use a third environment](/docs/developer-guides/project/multiple-environments#use-a-third-environment) ### Isolate changes and apply them to production This workflow applies a set of changes from your development environment to your master environment, as a controlled migration. It requires you to: 1. [Start a content freeze](/docs/developer-guides/project/content-freeze#configure-a-freeze) on your master environment. Since this workflow applies changes directly to master with no buffer environment, freezing content is what protects in-progress editor work here. 2. Make your changes and test them. 3. Use the **Management SDK method** to [isolate the changes](/docs/api-reference/management-sdk/management-sdk-batchmigration#generate-a-diff) and then [apply those changes to production directly](/docs/api-reference/management-sdk/management-sdk-batchmigration#apply-schema-changes). 4. [Lift the content freeze](/docs/developer-guides/project/content-freeze#lift-a-freeze-early) once the migration completes. This method has some limitations when it comes to applying schema changes to your master environment. See [this document](/docs/api-reference/management-sdk/management-sdk-batchmigration#how-schema-changes-affect-the-target-environment) for details. This workflow is generally safe: running the changes as a single migration means they succeed or fail as a whole, not individually, which reduces errors. If any step fails, Hygraph undoes all changes automatically. Use this workflow if you're on the **Growth plan**, since it includes only two environments total, including master. It doesn't guarantee against issues, because you're applying a new schema to content that's already changed. ### Use a third environment ![Promoting environments flowchart](/images/docs/user-guides/multiple-environments/multiple-environments-promoting-envs-flowchart.png) This workflow takes more steps, but it's the safest option. It requires you to: 1. Make your changes and test them. 2. Isolate your changes. Use [environment diffing](/docs/api-reference/schema/environment-diffing) to do this, or find and isolate them yourself if you kept track of them in code. 3. [Start a content freeze](/docs/developer-guides/project/content-freeze#configure-a-freeze) on your master environment. This prevents editors from creating, editing, or publishing content while you promote `staging` to master. 4. Clone a third environment named `staging` from `master`, right before deploying your feature. Cloning close to deployment time means `staging` carries master's most recent content. 5. Apply the changes you've made in your development environment to `staging` too. Use the **Management API**, as shown [here](/docs/api-reference/schema/environment-diffing#3-apply-schema-changes). For small schema changes, like deleting or renaming a field, applying them manually may be more practical. 6. Run tests on your `staging` environment to confirm there are no conflicts between the new schema and the updated content. 7. Promote your new `staging` environment to be the new master environment, containing the most recent content and schema changes. See [Promote the development environment to master](/docs/developer-guides/project/multiple-environments#promote-the-cloned-environment-to-master) for details. 8. [Lift the content freeze](/docs/developer-guides/project/content-freeze#lift-a-freeze-early). Editors regain the ability to save and publish immediately. If you run into issues with this method, roll back by promoting the old master environment to master again. This may require rolling back your frontend too. This method has some limitations when it comes to applying schema changes to your master environment. See [this document](/docs/api-reference/schema/environment-diffing#environment-diffing-limitations) for details. ## Promote the cloned environment to master Once you've made all the necessary changes to your application's codebase, promote the cloned environment to become the new master environment. Click **Promote** on the cloned environment, in **Project settings > Environments**. If you haven't already started a content freeze as part of the workflow above, start one now before promoting. This prevents editors from losing in-progress work in the environment being replaced. See [Content Freeze](/docs/developer-guides/project/content-freeze#configure-a-freeze). ![Promoting the Development Environment to Master](/images/docs/user-guides/multiple-environments/multiple-environments-promote-to-master.png) You need to rename the old master environment to avoid a naming clash. Filling in **New display name** auto-fills **New API ID**. ![Promoting the Development Environment to Master](/images/docs/user-guides/multiple-environments/multiple-environments-promotetomaster-screen.png) Click **Promote** to confirm. Hygraph renames the cloned environment to `master`, and it becomes your master environment from that point on. After promoting, deploy your code changes so your production websites or applications match the newly promoted environment. To minimize downtime, automate promotion and deployment in a CI/CD pipeline using the Management API or SDK. Coordinate deployment order between your Hygraph project and your frontend applications. When you add models or fields, deploy the Hygraph schema changes first, then the frontend. When you remove models or fields, reverse that order. This prevents your frontend from requesting data that doesn't exist yet. ### Tokens and environment promotion ![Permanent Auth Token](/images/docs/user-guides/multiple-environments/multiple-environments-token.png) Tokens hold two kinds of information: an `ID` and the environment it's associated with (`master`, `staging`, `development`). To see this yourself, copy a token from your project and paste it into jwt.io. Under `Payload: data` on the right, you'll find the environment your token targets. ![Permanent Auth Token](/images/docs/user-guides/multiple-environments/token-audience.png) To keep a token working after you promote an environment, create it in your master environment first, then clone that environment to [set up your development environment](/docs/developer-guides/project/multiple-environments#set-up-a-development-environment). Cloning an environment clones its tokens too, so the new environment's tokens share the same internal `ID` as the originals. After promotion, these tokens keep working, because the environment and the `ID` still match. Once you promote `development` to `master`, the token tied to the `development` environment stops working. If you later clone `master` again to create a new `development` environment, that token starts working again, since the project `ID` and environment match once more. ## Related docs - [Management SDK](/docs/api-reference/management-sdk/management-sdk): Apply schema changes programmatically. - [Environment diffing](/docs/api-reference/schema/environment-diffing): Isolate schema changes between environments. - [Environments API reference](/docs/api-reference/basics/environments): Manage environments through the API. --- # Share your project Source: https://hygraph.com/docs/developer-guides/project/share-your-project You can share your existing project's master environment as a template with others. Inside your project settings, you can **enable public cloning**. You can then decide whether you want to share existing content, assets, and/or webhooks. ![Project settings](/images/docs/user-guides/project-settings-share.png) Anyone with the public cloning link can clone this project into their own accounts. Check out our document on [Public cloning](/docs/developer-guides/project/clone-your-project#public-cloning) to know more. You can use this to create your own starter project and share it with other people. --- # Update your password Source: https://hygraph.com/docs/developer-guides/project/update-your-password To change your account password you will need to logout, and choose "forgot password" from the login page to send you an email to set a new one. If you're logged in with Facebook, GitHub, or Google, you can change your password there and you'll be logged in automatically with Hygraph. --- # Usage dashboard Source: https://hygraph.com/docs/developer-guides/project/usage-dashboard The Usage dashboard gives you direct visibility into your project's API and asset consumption. If you see any unexpected behavior, such as slow responses, errors, or limits being hit, this is where you start. To access it, navigate to **Project Settings > General > Usage**. ![Usage dashboard](/images/docs/developer-guides/project-guides/usage-dashboard.png) Use this dashboard to: - Rule Hygraph in or out during incident triage without filing a support ticket. - See whether a spike is hitting a plan quota or an operational limit, and which metric specifically. - Track API calls and asset bandwidth per project against your plan limits. - Monitor RPS, concurrency, and request size per environment before they cause dropped requests. - Identify which environment is driving consumption without cross-referencing external tools. ## Access The **Usage** tab is visible to Admin and Developer roles by default. To grant access to other users, assign them the **Read observability data** [Management API permission](/docs/getting-started/access-and-permissions/management-api-permissions#roles-experimental-ai-usage). Data in this dashboard is updated every hour. To query the same metrics programmatically, see [Usage metrics API reference](/docs/api-reference/basics/usage-metrics). ## Project quotas Project quotas show how much of your plan's allocated resources your project has consumed over your billing period. These metrics are scoped to the project, not individual environments. Approaching a limit here means you are approaching a plan boundary, not an operational threshold. A visual threshold indicates when consumption is approaching the project's limit: - Orange: 80% of the limit - Red: 95% of the limit If you are consistently approaching or exceeding your plan limits, see [Manage plan](/docs/developer-guides/project/update-billing#manage-plan). ### Asset bandwidth The **Asset bandwidth** chart shows the total data transferred for assets, such as images, video, and documents, served from your project. The chart shows daily consumption as a bar chart and a cumulative indicator. Hover over any bar to see the exact value for that day. Example: 40 / 100 GB shows the total asset bandwidth for your billing period against your plan limit. ![Asset bandwidth](/images/docs/developer-guides/project-guides/usage-dashboard-asset-bandwidth.png) ### API calls The **API calls** chart shows total requests made to your Content API. The chart shows daily consumption as a bar chart and a cumulative indicator. Hover over any bar to see the exact value for that day. Example: 47K / 500K calls shows the total API calls for your billing period against your plan limit. ![API calls](/images/docs/developer-guides/project-guides/usage-dashboard-api-calls.png) ## Tech safeguards metrics Tech safeguards metrics show the operational limits that govern how your API handles traffic at a given moment. These limits exist independently of your plan quotas. Exceeding them affects request behavior directly. For more information on tech safeguards in your plan, see [API limits](/docs/api-reference/basics/api-limits). You can filter tech safeguard metrics by environment and time range. - **Environment**: Filters all metrics to a specific environment. - **Time range**: Available options are last 7 days, last 30 days, last 6 months, and last 12 months. Metrics reflect activity up to the last hourly update. For same-day activity, check back after the next update cycle. Real-time data and minute-level granularity are not available. ### Request Activity A spike in blocked requests is the clearest signal that something hit an operational ceiling. The **Request Activity** chart shows two series: requests within limit and requests blocked. This lets you see exactly when that happened and how sustained it was. Hover over any point to see a breakdown tooltip for that day. ![Request Activity](/images/docs/developer-guides/project-guides/usage-dashboard-request-activity.png) ### Concurrent operations The **Concurrent operations** chart shows separate sparkline charts for concurrent queries and mutations against your project's concurrency limit. Each chart provides concurrent requests data for a given day. Hover over the limit line on any chart to see the following details for a day: - Maximum concurrent queries / mutations that day - Your plan's concurrency limit - Number of requests blocked that day If your concurrent requests cross your plan limit, they may be blocked. A red dot on the chart indicates that requests were blocked that day. For more information on concurrency operations in your plan, see [API limits - Concurrent operations](/docs/api-reference/basics/api-limits#concurrent-operations). ![Concurrent operations](/images/docs/developer-guides/project-guides/usage-dashboard-concurrent-operations.png) ### Request size The **Request size** chart shows separate sparkline charts for query and mutation request sizes against your project's configured limit. Hover over the limit line on any chart to see the following details for a day: - Maximum request size for queries / mutations that day - Your plan's request size limit - Number of requests blocked that day Requests that exceed the size limit may be blocked before execution. For more information on request size in your plan, see [API limits - Request size](/docs/api-reference/basics/api-limits#request-size). ![Request size](/images/docs/developer-guides/project-guides/usage-dashboard-request-size.png) ### RPS (Requests per second) The **RPS** chart shows query and mutation requests per second against your project's rate limit as a sparkline. Hover over the limit line to see the following details for a day: - Maximum RPS recorded that day - Number of requests blocked that day Sustained activity near the threshold indicates your client is sending requests faster than the project is configured to accept. This does not necessarily mean that requests are failing yet, but that the margin is reduced. A red dot on the chart indicates that requests were blocked that day. For more information on requests per second in your plan, see [API limits - Requests per second](/docs/api-reference/basics/api-limits#requests-per-second). ![RPS](/images/docs/developer-guides/project-guides/usage-dashboard-rps.png) --- # Remote data overview Source: https://hygraph.com/docs/developer-guides/remote-data/overview ## Overview ![remote-sources-mainscreen.png](/images/docs/user-guides/remote-sources/remote-sources-mainscreen.png) A Remote Source is a custom resolver entry point for your schema that allows data to be sourced from an external third-party web service and accept field values from other Hygraph fields as arguments. With Remote Sources, our [Content Federation](/docs/getting-started/fundamentals/content-federation) utility, it's possible to add content from other systems and sources to the Hygraph API without having to migrate the content itself. You can use Remote Sources to add Remote Fields to your schema, at the model or at the top level: At the model level, you can use them to enrich data, which can then be queried in the context of the model they are a part of. At the top level, you can use them to fetch [remote data](/docs/developer-guides/remote-data/remote-content) - unrelated to Hygraph - that can be consumed by your frontend alongside your Hygraph data. ## What you can do Remote Sources enable teams to build optimized, data-rich applications where they can use existing services paired with new microservices to build user-friendly applications with a single content entry point. There are many use cases where moving content into the CMS isn't possible or desirable. Examples include content for which there is a different system of record (e-commerce/product data like price or availability, content from a different vendor like Github or IMDBT, etc) and content that is stored in legacy systems that can't be easily migrated. Remote Sources allow customers to create a single GraphQL API with content from all these different sources, providing flexibility for front-end developers and downstream applications. ## Important concepts ### Remote Source A system or product that holds content that needs to be combined with content in Hygraph and which can be queried through a RESTful or GraphQL API. A single Remote Source can have different types of content (e.g., an e-commerce system might have products, categories, prices, etc). Every Remote Source has a unique (base) URL, such as Github, Shopify, Hasura, or a custom backend application. ### Remote content These are fields that you can add to a model to fetch data from a Remote Source. There are two types: Remote Fields and Top-level Remote Fields. ### Remote Field A field inside a Hygraph model that connects specific remote data to an entry of that model. Remote Fields are always related to a single Remote Source and a single custom type. RESTful Remote Fields are configured with a path to a specific endpoint in the Remote Source, such as user details from Github, or price & availability from Shopify. ### Top Level Remote Field A field inside the Query system model in Hygraph. These fields are used to fetch remote data outside the context of a regular model, which is then sent to your frontend alongside your Hygraph data. This [Content Federation](/docs/getting-started/fundamentals/content-federation) utility eliminates the need to make separate API requests for data inside & outside of Hygraph. ### Custom Type A GraphQL type that is used for content coming from a Remote Source. The custom types are combined with the auto-generated types inside Hygraph to create a single schema for content inside Hygraph and in the Remote Source. For RESTful Remote Sources, custom types need to be defined explicitly using SDL for all URL paths that will be queried in the Remote Source. For GraphQL Remote Sources, the custom types are auto-generated by using introspection on the Remote Source. ### Custom Input type A specific kind of GraphQL type which is used to define input parameters for queries to Remote Sources. --- # Add remote data to your model Source: https://hygraph.com/docs/developer-guides/remote-data/remote-content ## Overview This document section explains how to add Remote Fields to models in your project. The flow is the same for [Remote Fields](/docs/developer-guides/remote-data/remote-field#remote-fields) added to regular models, and [Top-level Remote Fields](/docs/developer-guides/remote-data/remote-field#top-level-remote-fields). If you don't know the difference between Remote Fields and Top-level Remote Fields, [check out this document](/docs/developer-guides/remote-data/remote-field). ## Add Remote Field After [adding a Remote Source](/docs/developer-guides/remote-data/remote-sources), it's now time to add a Remote Field to a model. This is slightly different for RESTful remote sources vs. GraphQL Remote Sources, so we will explain this step for each of them separately. Remote Fields & Top-level Remote Fields can only be added [after at least one Remote Source of the corresponding type - REST or GraphQL - has been configured](/docs/developer-guides/remote-data/remote-sources). ### REST First, select a Remote Field type: - If you're adding a [Remote Field](/docs/developer-guides/remote-data/remote-field#remote-fields) to a regular model: Navigate to the **Schema builder**, select the model that will contain your Remote Field, scroll down the field type list located on the right side of the screen, and select the `REST` field. - If you're adding a [Top-level Remote Field](/docs/developer-guides/remote-data/remote-field#top-level-remote-fields) to the `Query` model: Navigate to the `Query` model in your project schema, then select the `REST` field from the **Add fields** list located on the right side of the screen. ![REST Remote Field](/images/docs/user-guides/remote-sources/creating-remote-field-rest.png) Then, follow these instructions: 1. In the **Create Field** dialog, fill in the `Display name`, `API ID`, and optionally add a `Description`. 2. If the remote API for this field returns an array of the chosen custom type instead of a single object, make sure to select the `Allow multiple values` checkbox, under **Field options**. For instance, if you defined your custom type to be `Product`, but the remote API returns an array of `products`, you need to make use of the `Allow multiple values` option, so the request won't return an error. 3. Select a previously configured `Remote Source` and an `HTTP Method` using the dropdowns. For `Return type`, select one of the custom types that you configured for the Remote Source. This custom type needs to (partially) match with the response of the API path that will be requested in this field. You can find detailed information on creating custom types [here](/docs/developer-guides/remote-data/remote-sources#create-a-custom-type-definition). Alternatively, it's possible to set the response to be a scalar type (string, Boolean, JSON, etc). [Click here to learn how to use a custom type definition.](/docs/developer-guides/remote-data/remote-content#how-to-use-a-custom-type-definition) Adding a [Remote Source](/docs/developer-guides/remote-data/remote-sources) to your project is a prerequisite in order to add a remote field to a model. [Defining your custom types](/docs/developer-guides/remote-data/remote-sources#create-a-custom-type-definition) is the step of remote source configuration where you specify the shape of the response coming from a `REST` API. It's what allows you to query the `REST` API as if it were native `GraphQL`. These Custom Type Definitions use `GraphQL SDL` (Schema Definition Language). 4. You can optionally add `Input arguments`. You can add an input argument by selecting a custom input type for the Remote Source and providing an `API ID` for the inputs, which can be used in the configuration of the `URL Path`. Multiple input arguments can be added by clicking `+Add`. [Click here to learn how to use a custom input type definition.](/docs/developer-guides/remote-data/remote-content#how-to-use-a-custom-input-type-definition) [Here's an example of working with custom input types.](/docs/developer-guides/remote-data/remote-data-examples#working-with-custom-input-types) [Defining your custom types](/docs/developer-guides/remote-data/remote-sources#create-a-custom-type-definition) is the step of remote source configuration where you specify the shape of the response coming from a `REST` API. It's what allows you to query the `REST` API as if it were native `GraphQL`. These Custom Type Definitions use `GraphQL SDL` (Schema Definition Language). 5. Configure the `Path` that will be queried for this Remote Field. This path will be added to the Remote Source base path to get a resulting endpoint. In the path definition, you can use handlebars notation (start by typing a `{`) to use fields from the document or from the input arguments, if defined. This way, you can dynamically build a URL path using field values from the same content model or from an input parameter value. As an example, if the model has a field called `userId` , it's possible to build a path that looks like this: `/users/{{doc.userId}}/`repos. You can make it possible for Editors to select the input arguments. Imagine you've just configured a REST remote source where you added an input type definition called "articles", and want your Editors to be able to select the content entries of a specific user. What you'd do here, is add a single line field field, let's call it `username`, and then add your REST remote field, which we'll call `articles` for this example. We'd follow the same steps that we explained above for the `Remote source`, `Method` and `Return type`. We won't add an `Input argument` here. Instead we'll go to `Path`, and at the end of the URL path that we type in, we'll write `doc` to indicate that we're referencing the document we're editing, and select `{{doc.username}}`, where `username` corresponds to the field that we added before as an input. What we just did is add a REST Remote Field to our model in a way it can use metadata from a document to query it. In this example, what Editors would do is simply input a `username` in the content entry, and this would result in only `articles` from that `username` being fetched in a query. Would you like to see this more in depth? [Check out Tim's video](/docs/developer-guides/remote-data/remote-data-examples#add-any-rest-source). ### GraphQL At the moment, **Hygraph Studio** does not support this feature. First, select a Remote Field type: - If you're adding a [Remote Field](/docs/developer-guides/remote-data/remote-field#remote-fields) to a regular model: Navigate to the **Schema builder**, select the model that will contain your Remote Field, scroll down the field type list located on the right side of the screen, and select the `REST` field. - If you're adding a [Top-level Remote Field](/docs/developer-guides/remote-data/remote-field#top-level-remote-fields) to the `Query` model: Navigate to the `Query` model in your project schema, then select the `REST` field from the **Add fields** list located on the right side of the screen. ![Remote Field - GraphQL](/images/docs/user-guides/remote-sources/creating-remote-field-gql.png) Then, follow these instructions: 1. In the `Create Field` dialog, fill in `Display name`, `API ID`, and optionally add a `Description`. 2. Select a previously configured `Remote Source` and an HTTP `Method`. 3. You can **optionally** add `Input arguments`. You can add an input argument by selecting a `custom input type` for the remote source, and providing an `API ID` for the inputs, which can be used in the configuration of the URL `Path`. Multiple input arguments can be added by clicking on `+Add`. [Click here to learn how to use a custom input type definition](/docs/developer-guides/remote-data/remote-content#how-to-use-a-custom-input-type-definition). [Here's an example on working with custom input types](/docs/developer-guides/remote-data/remote-data-examples#working-with-custom-input-types). 4. Now select the `Query` that will be the entry point into the remote schema from the tree that is shown at the bottom of the `Create Field` dialog. This tree is populated using introspection, and will show all available queries in the Remote Source. - When selecting a query, the tree unfolds to show all `arguments` for that query (in purple), available `sub-queries` (enabled and showing type in blue), and available `fields` or `scalars` (disabled and showing with type in grey). It's important to note that the selected (sub)query will determine which data from the remote source can be queried through Hygraph. **All arguments, scalars, and subqueries in the Remote Source that are below the selected query will be queryable**. Other values and queries in the tree will _not_ be queryable unless they are part of another Remote Field. - Arguments that are required show a purple asterisk (\*) next to their ID, although there is no validation on the value done inside Hygraph. It's possible to use handlebars notation inside a parameter field. Start by typing `{`, which will bring up suggestions based on the fields on your model. ![GraphQL query selection](/images/docs/user-guides/remote-sources/graphql-query-selection.png) - For queries that return a single value, it's also possible to select a sub-query as the entry point. **Note that this means that only fields inside the selected sub-query are available to be queried through Hygraph.** ## How to use a custom type definition [After successfully defining the custom type for your remote source](/docs/developer-guides/remote-data/remote-sources#create-a-custom-type-definition), it can be used on your Remote Field (REST). 1. Open any of your models and either create a new Remote Field (REST) from the right hand field picker or edit an existing Remote Field (REST). On that Remote Field, select the just created custom type under Return type. ![Using a custom type definition](/images/docs/user-guides/remote-sources/using-custom-type-definition.png) 2. When querying your Remote Field, you will now have a sub-selection of the fields you defined. In this case, the Remote Field is called `githubInfo` ```graphql { authors { id name githubInfo { id name url } } } ``` ## How to use a custom input type definition 1. On your Remote Field, you can now select the [custom input type you defined for your remote source](/docs/developer-guides/remote-data/remote-sources#create-a-custom-input-type-definition) as an `Input Argument`. For this, open one of your models and add a new Remote Field or edit an existing one. 2. Click on `+Add` under `Input arguments`, select the input type you just created, and give it an API ID. ![Using a custom input type definition](/images/docs/user-guides/remote-sources/using-custom-input.png) 3. After saving the field, you can head into the API Playground and test the just created input argument, which will work like this: ```graphql { pages { id product(product: { productId: "123" }) { name slug price } } } ``` ## Advanced settings (Cache) Advanced settings apply to both types of Remote Fields. 1. Although `HTTP` headers can be configured on a **Remote Source** - meaning on all requests for all fields that use this remote source - it's also possible to add additional `HTTP` headers on a specific **Remote Field**. The headers are additive, but if you configure the same header both on the field and on the remote source, the value from the Remote Field will take precedence. Additionally, it's possible to have all client headers to Hygraph forwarded to the Remote Source. This can be useful to forward user context to the remote server, for example. 2. By default, Hygraph caches queries that include Remote Fields using a TTL cache with a value of 15 minutes. The TTL can be overridden in the Remote Field settings dialog (minimum TTL value is 60 seconds). However, please note that if the Remote Source sends a cache-control response header, this will override the cache configuration in Hygraph. 3. Optionally set field visibility. For the default setting of `read-only`, the Remote Field is displayed in the content form with a link to the API playground. If the field visibility is set to `API only`, the Remote Field is not displayed in the content form but is still available to query through the API. ## Query remote data ### Query Remote Fields After configuring the Remote Field, it's added to the Hygraph schema and immediately queryable through the API. Press `CTRL/CMD+Space` or open the Explorer view to see the available sub-fields inside the Remote Field. Note that the Remote Source prefix is added in front of the type for easy identification. Below screenshots demonstrate what this looks like for the User endpoint of the Github API: ![Remote Field - Query](/images/docs/user-guides/remote-sources/remote-field-query.png) ![Remote Field - Explorer](/images/docs/user-guides/remote-sources/remote-field-explorer.png) The following example fetches information from within a model: ```graphql { products { slug fieldremoteproducts { id slug } } } ``` ```graphql { "data": { "products": [ { "slug": "hoodie", "fieldremoteproducts": { "id": "cl3jub8gkslb90euj2n3ea357", "slug": "hoodie" } } ] } } ``` In this case, the Remote Field is related to the `Products` model, and it only fetches data related to it. ### Query Top Level Remote Fields Top Level Remote Fields can be queried outside the context of a model. In the following example, `product` is not a model, but a Top Level Remote Fields, completely unrelated to Hygraph content. ```graphql { products { id slug } } ``` ```graphql { "data": { "products": [ { "id": "clilfku1s007r0bvoci5ho12i", "slug": "hoodie" } ] } } ``` Instead of just enriching content that is in Hygraph, you can use the Hygraph API as a passthrough layer, where your frontend makes requests to APIs that don't relate to Hygraph. With this [Content Federation](/docs/getting-started/fundamentals/content-federation) feature, you can use Hygraph to pipe everything through to your frontend without the need of making two separate requests. Let's compare this to fetching information from within a model: ```graphql { products { slug fieldremoteproducts { id slug } } } ``` ```graphql { "data": { "products": [ { "slug": "hoodie", "fieldremoteproducts": { "id": "cl3jub8gkslb90euj2n3ea357", "slug": "hoodie" } } ] } } ``` In this case, the Remote Field is related to the `Products` model, and it only fetches data related to it. --- # Examples Source: https://hygraph.com/docs/developer-guides/remote-data/remote-data-examples ## Custom remote source and field configuration In this video, Bryan uses Federate this - our collection of mock APIs and information for federated data sources in GraphQL - to show you how to add data from a remote API to your Hygraph project step-by-step. ### Configure the remote source Navigate to the **Schema builder** and click on `+Add` next to **Remote sources** to create a new one. Use the following information: | Field | Input | | -------------- | ---------------------------------- | | `Display name` | Third Party APIs | | `Prefix` | ThirdPartyApIs | | `Type` | Select `REST` | | `Base URL` | `https://www.federatethis.com/api` | **Custom type definition:** Click `+ Add custom type definition` and we'll paste the product type and products array from Federate This: ```graphql type Product { id: Int name: String price: Int description: String image: String category: String rating: Float numReviews: Int countInStock: Int } type Products { data: [Product] } ``` We'll also add the following under `Products`: ```graphql type ProductMeta { data: Product } ``` What we just added will have a data field that will bring a singular product. Click `Add` at the top-right corner of your screen to create the remote source. Once the remote source is created, you can use it to add a remote field. ### Configure remote field After configuring our remote source, go to **Schema > Query** and add a top-level remote field, which will allow us to connect our products list as data directly in our API. To add your remote field, click on `REST` from the right sidebar and use the following information: | Field | Input | | --------------- | ------------------------------------------------------------ | | `Display name` | Products | | `API ID` | products | | `Remote source` | `Third Party APIs` is selected by default. Leave it as it is | | `Method` | `GET` is selected by default. Leave it as it is | | `Return type` | Use the dropdown to select `ProductMeta` | | `Path` | `/products` | Click `Add` at the bottom of the screen to create your remote field. Go to the API Playground and try out the following query: ```graphql query MyQuery { products { data { name price id } } } ``` ```graphql { "data": { "products": { "data": [ { "name": "Product 1", "price": 100, "id": 1 }, { "name": "Product 2", "price": 200, "id": 2 }, { "name": "Product 3", "price": 300, "id": 3 } ] } } } ``` If you run the query, it should return information on three sample products. If we wanted to bring specific information into a **Product** model, we'd have to add a Remote Field into that model. If you do not have a **Product** model yet, you can go to your Schema and click `+ Add` next to Models and use the following information to create it: | Field | Input | | --------------- | ------------- | | `Display name` | ProductPage | | `API ID` | productPage | | `Plural API ID` | productsPages | Let's also add some fields to this model. We won't be adding price or any other information that is otherwise in out API. We will only add fields that we want to add to our data and that Editors will be able to work with. We'll start with a `single line text` field that we'll use as a title: | Field | Input | | -------------------- | -------------------- | | `Display name` | Title | | `API ID` | title | | `Use as title field` | Select this checkbox | We'll add a `slug` field: | Field | Input | | --------------- | ---------------------------- | | `Display name ` | Slug | | `API ID` | slug | | `Lowercase` | Leave this checkbox selected | We'll add a `Rich Text` field: | Field | Input | | -------------- | ----- | | `Display name` | Body | | `API ID` | body | We need to associate the data in this model with the data that's in our API, and for that we'll add another `single line text` field, using the following information: | Field | Input | | -------------- | ------ | | `Display name` | API ID | | `API ID` | apiId | At this point we're ready to add our remote field. We'll scroll down the right sidebar and add a `REST` field using the following information: | Field | Input | | --------------- | ------------------------------------------------------------ | | `Display name` | Product data | | `API ID` | productData | | `Remote source` | `Third Party APIs` is selected by default. Leave it as it is | | `Method` | `GET` is selected by default. Leave it as it is | | `Return type` | Use the dropdown to select `ProductMeta` | | `Path` | `/product/{{doc.apiId}}` | Click `Add` to save your remote field. Let's create an entry for our **ProductPage** model. Navigate to **Content editor > ProductPage** and click `+ Add entry`. We'll use the following information: | Field | Input | | -------- | --------------------------------------------------------------------------------------- | | `Title` | Product 1 with extra data | | `Slug` | product-1 | | `Body` | Use the dropdown tos elect Heading 2 and write "This is a headline". | | `API ID` | This is where we will write the API ID of our product. IN this example we'll write "1". | We'll click `Save & publish` and go to the API Playground to test this. Let's run the following query: ```graphql query MyQuery { productPages { apiId body { html } slug title productData { data { price rating numReviews description countInStock } } } } ``` ```graphql { "data": { "productPages": [ { "apiId": "1", "body": { "html": "

This is a headline

" }, "slug": "product-1", "title": "Product 1 with extra data", "productData": { "data": { "price": 100, "rating": 4.5, "numReviews": 10, "description": "This is product 1", "countInStock": 10 } } } ] } } ```
This query brings all the information we just added in to the content entry that we created, which comes from Hygraph's data, and also includes the productData data, which comes from the API and is associated through the `API ID` field we added earlier. Now imagine you also want to add your product reviews here. Federate this has sample reviews that we can use to try this. We'll go back into the schema builder and edit our remote source by adding 3 new custom type definitions. In our remote source, we'll click on `+ Add new custom type definition`. We'll do this three times, to add the following types: ```graphql type Review { id: Int product: Int productSlug: String name: String rating: Float comment: String } ``` ```graphql type Reviews { data: [Review] } ``` ```graphql type ReviewMeta { data: [Review] } ``` Don't forget to click `Save` at the top-right of your screen to update your remote source. Next, we're going to add this to our **ProductPage** model as a `REST` remote field. To do this, navigate to **Schema builder > ProductPage**, select the `REST` field from the right sidebar, and use the following information: | Field | Input | | --------------- | ------------------------------------------------------------ | | `Display name` | Product Reviews | | `API ID` | productReviews | | `Remote source` | `Third Party APIs` is selected by default. Leave it as it is | | `Method` | `GET` is selected by default. Leave it as it is | | `Return type` | Use the dropdown to select `ReviewtMeta` | | `Path` | `/reviews/product/{{doc.apiId}}` | Click `Add` to save your remote field. The way we have set this up allows for the product details and the reviews from that product to pull from a single API ID, which we included in the `API ID` field that we added to our schema earlier and to which we assigned a value in the sample entry we created. Now we can go back to the API Playground to test this using the following query: ```graphql query MyQuery { productPages { apiId body { html } slug title productData { data { price rating numReviews description countInStock } } productReviews { data { comment name rating } } } } ``` ```graphql { "data": { "productPages": [ { "apiId": "1", "body": { "html": "

This is a headline

" }, "slug": "product-1", "title": "Product 1 with extra data", "productData": { "data": { "price": 100, "rating": 4.5, "numReviews": 10, "description": "This is product 1", "countInStock": 10 } }, "productReviews": { "data": [ { "comment": "These shoes are both the mostly comfortable and the most stylish I have ever owned.", "name": "Person Personson", "rating": 4.5 }, { "comment": "These would be better if they were red. I suppose they aren't bad, but they aren't good either.", "name": "Second Person", "rating": 2.5 }, { "comment": "I've worn these for 3 years and they are still in great shape. I'm going to buy another pair. I think everyone should have a pair of these shoes in their closet.", "name": "Another Great Person", "rating": 4.5 } ] } } ] } } ```
The response now contains the 3 sample reviews that were included in Federate This. ## Commercetools remote source configuration You need to have a commercetools account as well as have a project set up. ### Get your commercetools keys Before we start, there is some data we will need from your commercetools account: Go to **Settings > Developer settings > API clients**. Click `+ Create new API client` to create one from scratch, give it a name and select the scopes you need for your project. In our example we will select the `Project` scope under **Manage**. Click `Create API client` at the top-right of the screen. The resulting screen contains the information you will need to configure your remote source. Save it for later, and let's continue with the process. ### Commercetools Remote Source ![Commercetools Remote Source](/images/docs/user-guides/remote-sources/commercetools-remote-source.png) Navigate to the schema builder and click `+Add` next to **Remote Sources**. Use the following information to configure your remote source: | Field | Input | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Remote source type | Select **Commercetools** | | Authorization URL | This is the **Auth URL** in the API client details view of your commercetools account. Instead of copying it directly from there, copy the URL on the first line of the cURL below. Paste that in the Authorization URL field of your Hygraph Remote Source. | | Scopes | This is the **Scope** in the API client details view of your commercetools account. Copy it and paste it in Hygraph, then click `+ Add scope` to add. | | Client ID | This is the **client_id** key in the API client details view of your commercetools account. Copy it and paste it in Hygraph. This is like a username. | | Client Secret | This is the **secret** key in the API client details view of your commercetools account. Copy it and paste it in Hygraph. This is like a password. | | Display name | This is the display name of your Remote Source, we will write "Commercetools" here. | | Prefix | Completing the display name autocompletes the prefix, you can leave it as is. | | Type | Select `GraphQL` | | Base URL | To form the base URL, you will use the **API URL** and the **project_key** from the API client details view of your commercetools account, like this: `https:////graphql`. | Click `Add Remote Source` at the bottom of the screen to save your Remote Source. ## Commercelayer remote source configuration You need to have a Commercelayer account. ### Get your Commercelayer keys Before we start, there is some data we will need from your Commercelayer account: Go to **Applications > Add new**. Select a type, for this example we will select `Sales Channel`, give it a name, and click `Get credentials`. The resulting screen contains the information you will need to configure your remote source. Save it and let's continue with the process. ### Commercelayer Remote Source ![Commercelayer Remote Source](/images/docs/user-guides/remote-sources/commercelayer-remote-source.png) Navigate to the schema builder and click `+Add` next to **Remote Sources**. Use the following information to configure your remote source: | Field | Input | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Remote source type | Select **Commercelayer** | | Authorization URL | Copy the **Base endpoint** in your Commercelayer credentials screen, paste it in Hygraph, and add `/oauth/token` at the end, like this: `https:///oauth/token`. | | Scopes | We won't add anything here for this example. | | Client ID | Copy the **Client ID** in your Commercelayer credentials screen and paste it here. | | Client Secret | We won't add anything here for this example. | | Display name | This is the display name of your Remote Source, we will write "Commercelayer" here. | | Prefix | Completing the display name autocompletes the prefix, you can leave it as is. | | Type | Select `REST` | | Base URL | Copy the **Base endpoint** in your Commercelayer credentials screen and paste it here. | Click `Add Remote Source` at the bottom of the screen to save your Remote Source. ## Working with custom input types In this example, we will see how we can define and use a custom input type on a Remote Field. We will be using the Github REST API, specifically the `users` endpoint. ### Step 1: Configure Github as Remote Source We'll first add the Github API as a Remote Source and configure the right custom (input) types. Set up a new remote source. If you don't know how to do this, please [check out our documentation](/docs/developer-guides/remote-data/remote-sources), using `https://api.github.com` as the base URL, then add a custom type with the following SDL: ```graphql type User { avatar_url: String bio: String blog: String company: String created_at: DateTime email: String events_url: String followers: Int followers_url: String following: Int following_url: String gists_url: String gravatar_id: String hireable: Boolean html_url: String id: Int location: String login: String name: String node_id: String organizations_url: String public_gists: Int public_repos: Int received_events_url: String repos_url: String site_admin: Boolean starred_url: String subscriptions_url: String twitter_username: String type: String updated_at: DateTime url: String } ``` Also add a custom _input_ type with the following SDL: ```graphql input githubInput { userId: String! } ``` ### Step 2: Set up the Remote Field In an appropriate model, set up a REST Remote Field. In this example, we will use a model called `Author` and set up the Remote Field with apiId `githubUserDetails`. In the Remote Field config, add an input argument and select the input type to be the previously created `githubInput`. In the example, we will use the auto-generated API ID `githubInput` for the input arguments. We'll also set the field to be required since we will make the path depend on the input value. ![Example - Input argument](/images/docs/user-guides/remote-sources/github-example-input-arg.png) Now configure the Remote Field's path to use the input argument. You can use the autocomplete feature in the handlebars notation for this. ![Example - Remote Field path](/images/docs/user-guides/remote-sources/github-example-remote-field-path.png) ### Step 3: Query the field Make sure there is at least one entry for the selected model, and then head over to the API Playground. The input argument can now be used in a query in the following way: ```graphql query MyQuery { author(where: { id: "" }) { id githubUserDetails(githubInput: { userId: "hygraph" }) { name bio } } } ``` Please note that since we've made the `userId` parameter required in the custom input type (by adding a `!` at the end), there will be an error if we don't supply the value properly. ## Fetching a product catalog This example shows a product content entry that fetches the product catalog from a Remote Source. Besides the data that lives in Hygraph inside a regular model called `Product` - Product name, product description, etc - you can fetch a product catalog from an external API through a remote source that you added to the `Product` model as a Remote Field. ![Remote Field example](/images/docs/user-guides/remote-sources/example-remote-field.png) Requests will return information in Hygraph - `Product` model - as well as information in the Remote Source, called `myCatalogue` in our `Product` model for this example. ```graphql { products { id myCatalogue { id name slug } } } ``` ```graphql { "data": { "products": [ { "id": "clix9xjpu004n0btas02yx2ce", "myCatalogue": [ { "id": "cl3jub4i9sl9o0eujar33f9rm", "name": "T Shirt", "slug": "t-shirt" }, { "id": "cl3jub8gkslb90euj2n3ea357", "name": "Hoodie", "slug": "hoodie" } ] } ] } } ``` The frontend could then use this information received through the Hygraph Content API to display the product page enriched with the product catalog. ## Add any REST source This video tutorial covers adding a remote REST source into Hygraph. Tim shows us the two ways to query a REST remote source: - Adding a global instance for the source, so we can query it from anywhere in our code. - Using metadata to query from content editing in the CMS. ## Top-level Remote Fields This example uses Top-level Remote Fields to show us a product information content entry that fetches the product catalogue from an external API, unrelated to Hygraph. Besides the data that lives in Hygraph inside a regular model called **Product information** - `Slug`, `Title`, `Description` - you can fetch a product catalogue from an external API through a Remote Source that you add to the `Query` model as a Remote Field. ![Top-level Remote Field example](/images/docs/user-guides/remote-sources/example-top-level-remote-field.png) Requests will return information in Hygraph - `Product` model- as well as information in the Remote Source, called `fieldremoteproduct` in our `Query` model for this example. ```graphql { products { slug description title } fieldremoteproduct { slug name } } ``` ```graphql { "data": { "products": [ { "slug": "hoodie", "description": "Wrap yourself in cozy comfort with this stylish hoodie. Crafted from soft, premium materials, it offers a perfect blend of warmth and casual fashion. The versatile design features a kangaroo pocket for added convenience, while the adjustable drawstring hood adds an extra layer of coziness. Whether you're lounging at home or heading out for a casual outing, this hoodie is a must-have addition to your wardrobe.", "title": "Red Hoodie" } ], "fieldremoteproduct": [ { "slug": "t-shirt", "name": "T Shirt" }, { "slug": "hoodie", "name": "Hoodie" } ] } } ``` The frontend could then use this information received through the Hygraph Content API to display the product page along with a product catalogue sourced from an external API. ## E-commerce platform This video tutorial covers adding a remote source to Hygraph and creating Top-Level Remote Fields to pull in data from the external e-commerce platform, BigCommerce. --- # Remote Fields vs Top-level Remote Fields Source: https://hygraph.com/docs/developer-guides/remote-data/remote-field ## Overview Remote Fields are fields that you can add to a model in order to fetch data from a [remote source](/docs/developer-guides/remote-data/remote-sources). There are two types: - **Remote Field:** A field inside a regular Hygraph model that connects specific remote data to an entry of that model. Remote Fields are always related to a single remote source and a single custom type. RESTful remote fields are configured with a path to a specific endpoint in the remote source, such as user details from Github, or price & availability from Shopify. - **Top Level Remote Field:** A field inside the `Query` system model in Hygraph. These fields are used to fetch remote data outside the context of a regular model, which is then sent to your frontend alongside your Hygraph data. This [Content Federation](/docs/getting-started/fundamentals/content-federation) utility eliminates the need to make separate API requests for data inside & outside of Hygraph. ## Remote field types You can use [Remote Fields](/docs/developer-guides/remote-data/remote-field#remote-fields) to enrich data by fetching information from a [Remote Source](/docs/developer-guides/remote-data/remote-sources). This data can be queried in the context of the model it's a part of. [Top-level Remote Fields](/docs/developer-guides/remote-data/remote-field#top-level-remote-fields) fetch data that's unrelated to Hygraph and pass it through in the same API call as the data that is related to Hygraph content. This is what we'd call a [Content Federation](/docs/getting-started/fundamentals/content-federation) gateway case, where your frontend will receive everything as if coming from Hygraph, but the data itself can come from diverse sources. ### Remote Fields ![Remote Fields - Model view](/images/docs/user-guides/remote-sources/remote-fields-model-view.png) Remote Fields are fields inside a Hygraph model that connect specific remote data to an entry of that model. These fields are always related to a single Remote Source and a single custom type. RESTful Remote Fields are configured with a path to a specific endpoint in the Remote Source, such as user details from Github or price & availability from Shopify. #### What you can do with Remote Fields You can add Remote Fields to regular models in your schema to enrich data. #### Possible use cases for Remote Fields - They can facilitate e-commerce by consolidating product listings from multiple suppliers onto one platform. - In travel, they can integrate flight, hotel, and rental car information from numerous providers. - In stock management, they can provide real-time inventory data from various warehouses, enabling efficient tracking and management. ### Top-level Remote Fields ![Top-level Remote Fields - Query model](/images/docs/user-guides/remote-sources/top-level-remote-fields-query-model.png) Top-level Remote Fields use the `Query` model, which is a custom resolver entry point for your schema that allows you to fetch remote data alongside the regular models in Hygraph. You can find the Query model by navigating to the schema builder of your project. These Top-level Remote Fields exist in their own model in the schema - the Query model - and you can use them to go beyond the content enrichment case by passing data that is unrelated to the content you have in your Hygraph project. We called this the `Query` model because in GraphQL query is the root type related to reading data. This is also why adding Remote Fields to the `Query` model makes them available at the top level. #### What you can do with Top-level Remote Fields - You can use this model to pass data through our Content API without the need for it to be associated to a Hygraph content entry. In other words, you can use this model as a top-level entry point to fetch external data. - Take advantage of our [Content Federation](/docs/getting-started/fundamentals/content-federation) capabilities by integrating more systems and using them independently from their Hygraph content. #### Possible use cases for Top-level Remote Fields - Data ownership needs to stay within the external system rather than being mirrored into another system. - You are working with a multi-layered infrastructure where data enrichment happens before the data reaches Hygraph. - Your website makes calls to APIs that are not related to Hygraph. - You are working on a project where not all content entries need enrichment. --- # Remote Source configuration Source: https://hygraph.com/docs/developer-guides/remote-data/remote-sources ## Add a remote source ![Remote Sources - Select a type](/images/docs/user-guides/remote-sources/remote-source-select-type.png) The first step to adding a remote source to your project is selecting the type. You can create a custom source or use a pre-built one. Hygraph offers pre-built remote sources for CommerceTools and CommerceLayer. 1. Navigate to the Schema Builder. 2. In the left sidebar, find the Remote Sources section at the bottom of the list, then click +Add. 3. Use the selection boxes at the top of the screen to select a remote source type: - Custom - CommerceTools - CommerceLayer ### Add a custom source After selecting a custom remote source, you can complete the following fields: | Field | Input | |-----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `Display Name` | Enter a display name here. This name will appear in Hygraph. | | `Prefix` | The prefix is auto-generated and will be added to all types that are created for this Remote Source to avoid name clashes. | | `Description` | You can optionally add a description of the Remote Source here. | | `Enable debugging` checkbox | You can enable debugging for this Remote Source, which will provide more information in case of errors. **Make sure to disable this once you finish setting up the Remote Source to avoid leaking sensitive data**. | | `Type` | Use the radio buttons to set the type to either `REST` or `GraphQL`, depending on the type of API that this remote source will connect to. More fields will display as a result of your selection. [Check out this document's “Select a type” section to learn more about configuring REST and GraphQL types](/docs/developer-guides/remote-data/remote-sources#select-a-type). | ### Add a commercetools source [Click here to view an example of how to configure a commercetools Remote Source](/docs/developer-guides/remote-data/remote-data-examples#commercetools-remote-source-configuration) After selecting a CommerceTools Remote Source, you will find that the screen now contains two distinct sections for you to complete. The first section is the OAuth configuration for CommerceTools:
| Field | Input | |-----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Authorization URL** | Enter your CommerceTools authorization URL here. Copy the **Base endpoint** in your CommerceTools credentials screen, paste it in Hygraph, and add `/oauth/token` at the end, like this: `https:///oauth/token`. | | **Scopes** | Provide one or more scopes and click `+Add`. This is the **Scope** in the API client details view of your commercetools account. Click here to learn more about Commercetools scopes. | | **Client ID** | Enter your CommerceTools `Client ID` here. This is the **client_id** key in the API client details view of your commercetools account. Copy it and paste it in Hygraph. This is like a username. | | **Client Secret** | Enter your CommerceTools `Client Secret` here. This is the **secret** key in the API client details view of your commercetools account. Copy it and paste it in Hygraph. This is like a password. | Once you've completed this, you can move on to the second section, where you will configure the details of your Remote Source: | Field | Input | |-------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Display Name** | Enter a display name here. This name will appear in Hygraph. | | **Prefix** | The prefix is auto-generated and will be added to all types that are created for this Remote Source to avoid name clashes. | | **Description** | You can optionally add a description of the remote source here. | | **Enable debugging** checkbox | You can enable debugging for this remote source, which will provide more information in case of errors. **Make sure to disable this once you finish setting up the Remote Source to avoid leaking sensitive data**. | | **Type** | Use the radio buttons to set the type to either `REST` or `GraphQL`, depending on the type of API that this remote source will connect to. More fields will display as a result of your selection. [Check out this document's “Select a type” section to learn more about configuring REST and GraphQL types](/docs/developer-guides/remote-data/remote-sources#select-a-type). | ### Add a Commercelayer source [Click here to view an example of how to configure a Commercelayer Remote Source](/docs/developer-guides/remote-data/remote-data-examples#commercelayer-remote-source-configuration) After selecting a CommerceLayer remote source, you will find that the screen now contains two distinct sections for you to complete. The first section is the OAuth configuration for CommerceLayer:
| Field | Input | |-----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Authorization URL** | Enter your CommerceLayer authorization URL here. Copy the **Base endpoint** in your Commercelayer credentials screen, paste it in Hygraph, and add `/oauth/token` at the end, like this: `https:///oauth/token`. | | **Scopes** | Provide one or more scopes and click `+Add`. This field is optional. Click here to learn more about Commercelayer scopes. | | **Client ID** | Enter your CommerceLayer `Client ID` here. Copy the **Client ID** in your Commercelayer credentials screen and paste it here. | | **Client Secret** | You can optionally enter your CommerceLayer `Client Secret` here. This field is optional. | Once you've completed this, you can move on to the second section, where you will configure the details of your Remote Source: | Field | Input | |-------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Display Name** | Enter a display name here. This name will appear in Hygraph. | | **Prefix** | The prefix is auto-generated and will be added to all types that are created for this Remote Source to avoid name clashes. | | **Description** | You can optionally add a description of the remote source here. | | **Enable debugging** checkbox | You can enable debugging for this remote source, which will provide more information in case of errors. **Make sure to disable this once you finish setting up the Remote Source to avoid leaking sensitive data**. | | **Type** | Use the radio buttons to set the type to either `REST` or `GraphQL`, depending on the type of API that this remote source will connect to. More fields will display as a result of your selection. [Check out this document's “Select a type” section to learn more about configuring REST and GraphQL types](/docs/developer-guides/remote-data/remote-sources#select-a-type). | ## Select a type ### REST The following fields display when you select the `REST` type: | Field | Input | |-----------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Base URL** | Enter a base URL here. All Remote Fields that connect to this Remote Source will use that base URL.

**Commercetools Base URL:** To form the base URL, you will use the **API URL** and the **project_key** from the API client details view of your commercetools account, like this: `https:////graphql`.

**Commercelayer Base URL:** Copy the **Base endpoint** in your Commercelayer credentials screen and paste it here. | | **Headers** | Optionally, you can include HTTP headers that will be added to all API requests for all Remote Fields connected to this Remote Source. Example use cases include authorization and accepted media types. | | **Custom type definitions** | You can now [define your custom types](/docs/developer-guides/remote-data/remote-sources#create-a-custom-type-definition) here. These provide the mapping from the API responses to the GraphQL schema. [Custom input types](/docs/developer-guides/remote-data/remote-sources#create-a-custom-input-type-definition) can optionally also be defined here. | ### GraphQL The following fields display when you select the `GraphQL` type: | Field | Input | |----------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Base URL** | Enter a base URL here. All Remote Fields that connect to this Remote Source will use that base URL.

**Commercetools Base URL:** To form the base URL, you will use the **API URL** and the **project_key** from the API client details view of your commercetools account, like this: `https:////graphql`.

**Commercelayer Base URL:** Copy the **Base endpoint** in your Commercelayer credentials screen and paste it here. | | **Headers** | Optionally, you can include HTTP headers that will be added to all API requests for all Remote Fields connected to this Remote Source. Example use cases include authorization and accepted media types. | | **Custom input type definition** | You can optionally provide an alternative introspection URL and custom headers to send to the introspection endpoint. If no separate introspection URL is provided, the base URL will be used (the default behavior for most GraphQL APIs is to allow querying and introspection on the same URL). [Custom input types](/docs/developer-guides/remote-data/remote-sources#create-a-custom-input-type-definition) can optionally also be defined here. | | **Introspection method** | Select `GET` or `POST`.

If `GET`, we do a `GET` request and include the introspection query in the URL as a query parameter.

If `POST`, we do a `POST` request and include the introspection query in the body. | | **Introspection URL** | If your introspection URL is different from the Remote Source URL, you can add it here. | | **Introspection headers** | Here, you can add the headers to pass along with the request to the remote source.

Simply add a key and value pair and click `+ Add header`. | ## Create a custom type definition When connecting to a remote `REST API`, you will have the option to define a **Custom Type Definition**, which allows you to specify the shape of the response coming from the API. It will allow you to query the `REST API` as if it were native `GraphQL`. These **Custom Type Definitions** use `GraphQL SDL` (Schema Definition Language). A `Schema Definition Language` or `SDL` is a way to define the shape of data that can be queried in a `GraphQL API`. It describes the schema or types of data available, their relationships, and how the data can be queried. We need to tell Hygraph the shape of the data to expect and make available. While a `REST API` can use multiple endpoints to return fixed data structures, a `GraphQL API` - not being limited to returning fixed structures - will expose just one. The custom type definitions that we add in this step allow us to specify the shape of the response coming from the API, so we can query the `REST API` as if it were native `GraphQL`. Click here to learn more about GraphQL SDL Follow these steps to create a custom type definition: 1. While [adding a new Remote Source](/docs/developer-guides/remote-data/remote-sources#add-a-remote-source) or editing an existing one, scroll down to `Custom Type Definitions` and click `+Add`. 2. You can use our JSON to SDL converter tool to transform the `JSON` response from your API into valid `GraphQL SDL`. You might need to make a few tweaks to the generated SDL, especially changing the `JSON` type to `Json` (note the difference in casing). Here's an example of what that would look like: ```json { "login": "name", "id": 9197688, "url": "https://api.github.com/users/name", "repos_url": "https://api.github.com/users/name/repos", "name": "John Doe", "company": "@ACME", "email": "mail@example.com" } ``` ```graphql type GithubUser { company: String email: String id: Int login: String name: String repos_url: String url: String } ``` 4. Add the SDL for your custom type to the input field. It's possible to add multiple types in a single input field, after saving the types will be moved to separate fields. 5. Click `Create` or `Save` on the top right corner. Custom types also support nesting, so they can make use of another type, as shown below: ```graphql type Product { name: String metaInfo: MetaInfo slug: String } ``` ```graphql type MetaInfo { createdAt: DateTime createdBy: String currency: String } ``` In this example, the custom `Product` type makes use of another custom type `MetaInfo`. ## Create a custom input type definition Remote Fields also allow you to pass along input parameters to your Remote Source endpoint. This can be useful if the identifiable information for the remote data isn't kept in Hygraph, but defined on a request basis. This is relevant for both the `REST` and GraphQL Remote Sources. To create such a definition and to use it in a query, you will need to follow these steps: 1. While [adding a new Remote Source](/docs/developer-guides/remote-data/remote-sources#add-a-remote-source), or editing an existing one, scroll down to `Custom Input Type Definition` and click on `+Add`. 2. Similar to the Custom Types, you need to use the GraphQL Schema Definition Language (SDL) to define what the input parameter will look like. A tool that could be used here is JSON2SDL, which allows translating a `JSON` object to a valid SDL. Keep in mind that we are not defining a type here, but an input. Let's take an example of passing a `productId` to our remote API, that will be used as an input argument. The SDL would look like this: ```graphql input productInput { productId: String! } ``` --- # Remote data troubleshooting Source: https://hygraph.com/docs/developer-guides/remote-data/troubleshooting ## Known issues and tips If you work with Remote Sources and you query a lot of entries at the same time, make sure to use sensible pagination limits within Hygraph, so requests to a third-party API are not rate limited or blocked. When defining a custom type, you can use all the default GraphQL Scalars such as String, Int, Float, Boolean, ID and additionally Json. `!` can be used to indicate a field not being nullable, `[Type]` will indicate a list (array). More info on the object types and fields can be found here: https://graphql.org/learn/schema/ ## Refreshing a GraphQL Remote Source If something changes on your GraphQL API - i.e., adding or removing fields, or changing types - you can refresh the Remote Source. ![Refetch remote schema](/images/docs/user-guides/remote-sources/refetch-remote-schema.png) To do this, navigate to the GraphQL Remote Source you want to refresh, click on the context menu, then click on `Refetch Remote Schema`. This sends the new introspection query through the API, and once it goes through successfully, you can use the new fields. If there is an error happening on refetching, please check the `Query` section of your Remote Source configuration screen. Make sure that your remote fields are not using a type that's been updated or removed, and that the return type is not using it either. [Our documentation on **Adding remote data to your model**](/docs/developer-guides/remote-data/remote-content) covers introspection queries. This issue can be avoided by working on additive changes. ## Error querying remote fields Irrespective of the model you selected, the default behavior for errors in querying Remote Fields is that the query to Hygraph will return successfully but with a `null` value for the Remote Field and an error message to indicate the Remote Field could not be queried. If the value of the Remote Field is critical for the proper functioning of the front-end application, it's possible to mark the Remote Field as `required`. With this setting enabled, the whole query to Hygraph will return an error if the Remote Field does not provide a successful response. ![Error querying remote fields](/images/docs/user-guides/remote-sources/remote-field-required-option.png) --- # Click to Edit setup Source: https://hygraph.com/docs/developer-guides/schema/click-to-edit Click to Edit lets editors jump from any tagged element in a content preview directly to that field in Hygraph Studio. You configure it on the frontend using the Hygraph Preview SDK. Editors perform the following steps without any further developer involvement: 1. Hover over a tagged element in preview to display an **Edit** button. 2. Click the **Edit** button to open the corresponding field entry in Studio. 3. Save changes to automatically refresh the preview. Click to Edit is built on top of live preview. You do not need live preview running before you start, but editors get the full visual editing experience only when both are set up. See [live preview setup](/docs/developer-guides/schema/live-preview) if you have not configured it yet. ## How it works The SDK operates in two modes that are automatically detected: - **Iframe Mode**: When your preview loads in an iframe inside Hygraph Studio. - **Standalone Mode**: When your preview loads in a separate browser tab outside Studio. In both modes, the SDK scans your rendered HTML for `data-hygraph-*` attributes. These attributes map each element back to a specific entry and field in Hygraph. The SDK adds hover overlays and **Edit** buttons automatically. When an editor saves in Studio, Hygraph sends a save event to the preview so it can refresh while preserving scroll position. Real-time field updates are disabled by default. The preview refreshes on save only. You can enable live updates with `sync={{ fieldUpdate: true }}`. ## Get started ### Availability - The Click to Edit feature is available for all projects and is currently in Beta. ### Prerequisites - A Hygraph project with at least one model and content entry. - A running preview application (local or deployed) using React, Next.js, Remix, Vue, or vanilla JavaScript. ### Setup steps 1. [Install the Preview SDK](#install-the-preview-sdk). 2. [Create a PreviewWrapper component to enable the preview functionality](#create-the-previewwrapper-component). 3. [Set environment variables](#set-environment-variables). 4. [Add data attributes to content elements](#add-data-attributes-to-content-elements). 5. [Set up the Preview widget in Studio](#set-up-the-preview-widget-in-studio). 6. [Verify the setup](#verify-the-setup). ## Install the Preview SDK The SDK connects your frontend to Studio. It handles overlay rendering, save event listening, and iframe/standalone mode detection. ```bash npm install @hygraph/preview-sdk ``` ## Create the PreviewWrapper component The `PreviewWrapper` component initializes the SDK across your application, and enables the preview functionality. Choose the implementation that matches your framework: - [Next.js App Router](/docs/developer-guides/schema/click-to-edit-next-js-app-router#create-the-previewwrapper-component) - [Next.js Pages Router](/docs/developer-guides/schema/click-to-edit-next-js-pages-router#create-the-previewwrapper-component) - [Remix](/docs/developer-guides/schema/click-to-edit-remix#create-the-previewwrapper-component) - [Vue](/docs/developer-guides/schema/click-to-edit-vue-nuxt#vue) - [Nuxt](/docs/developer-guides/schema/click-to-edit-vue-nuxt#nuxt) - [Vanilla JavaScript](/docs/developer-guides/schema/click-to-edit-vanilla-js) ### Configuration properties | Property | Required | Description | |---|---|---| | `endpoint` | Required | Your Hygraph Content API endpoint. See [Content API](/docs/developer-guides/project/api-access#content-api). | | `studioUrl` | Optional (recommended) | Studio base URL. Defaults to `https://app.hygraph.com`. Set this if your Studio runs on a regional or custom domain. | | `onSave` | Optional | Callback fired after Studio reports a save. Receives the saved entry ID. Use this to trigger revalidation or a router refresh. | | `debug` | Optional | Enables verbose console logging. Useful for diagnosing missing attribute issues. | | `mode` | Optional | Forces `'iframe'`, `'standalone'`, or `'auto'`. Auto-detection works for most cases. | | `overlay` | Optional | Customize overlay border color, border width, button background, and button text color. | | `sync.fieldFocus` | Optional | Synchronizes field focus between Studio and the preview when an editor selects a field. | | `sync.fieldUpdate` | Optional | Applies live field changes to the preview as the editor types. Defaults to `false`. | | `allowedOrigins` | Optional | Additional domains that can host the preview iframe, such as staging or QA environments. | ## Set environment variables Set the following values in `.env.local`. If you already configured these for live preview, skip this step. ```bash NEXT_PUBLIC_HYGRAPH_ENDPOINT=https://your-region.cdn.hygraph.com/content/your-project-id/master NEXT_PUBLIC_HYGRAPH_STUDIO_URL=https://your-region.hygraph.com HYGRAPH_TOKEN=your-permanent-auth-token ``` - **Content API endpoint**: Copy your Content API endpoint from your Hygraph project settings under **Project Settings > Access > Endpoints > High Performance Content API**. For more information, see our dedicated docs on the [Content API](/docs/developer-guides/project/api-access#content-api). - **Hygraph Studio base URL**: Copy the base Studio URL from your browser's address bar. Example: `https://studio-eu-central-1-shared-euc1-02.hygraph.com`. - **Permanent Auth Token**: You can create a Permanent Auth Token under **Project Settings > Access > Permanent Auth Tokens**. Check that the default content stage is DRAFT. For more information, see our dedicated docs on [Permanent Auth Tokens](/docs/developer-guides/project/api-access#permanent-auth-tokens). - This is needed only if your Hygraph project requires authentication for Content API requests. ## Add data attributes to content elements The SDK uses `data-hygraph-*` attributes to map rendered elements back to Hygraph fields. Add them to the JSX or HTML elements that render Hygraph content. You do not need to instrument every element; add attributes only to the fields editors need to edit from the preview. The SDK scans rendered HTML for these attributes and attaches hover overlays and **Edit** buttons automatically. The same attributes work for variants. When an editor opens a variant, clicking a tagged element focuses the corresponding field directly in the variant overlay. | Attribute | Required | Description | |---|---|---| | `data-hygraph-entry-id` | Required | The content entry ID. Every editable element needs this. Always use the root entry ID, not a component instance ID. | | `data-hygraph-field-api-id` | Optional | The API ID of the field in the schema. Without this, clicking **Edit** opens the entry without focusing a specific field. | | `data-hygraph-rich-text-format` | Optional | Format for Rich Text fields. Accepts `html`, `markdown`, or `text`. | | `data-hygraph-component-chain` | Optional | JSON array describing the path to a nested component field. See [tagging component fields](#tagging-component-fields). | ![`data-hygraph-entry-id` - Content entry](/images/docs/developer-guides/click-to-edit-data-attributes-simple.png) To find the `data-hygraph-field-api-id`: 1. Open **Schema** and select your model. 2. Locate the field. The API ID appears next to the field name in camelCase without any spaces. 3. Use that value as `data-hygraph-field-api-id`. ![`data-hygraph-field-api-id` - Schema](/images/docs/developer-guides/click-to-edit-data-attributes-api-id.png) ### Tagging component fields Components require the `data-hygraph-component-chain` attribute in addition to the standard attributes. This tells Studio how to navigate from the root entry to the specific nested component instance. The chain is a JSON array of `{ fieldApiId, instanceId }` objects ordered from outermost to innermost component. The `instanceId` is the unique identifier of the component instance returned in your GraphQL response. It is not the component type's API ID, and it does not appear in the Studio UI. You do not copy instance IDs from Studio. Query for the `id` field on each component in your GraphQL query. The SDK reads this value from your data at render time to build the chain. | Attribute | Description | |---|---| | `data-hygraph-entry-id` | The root content entry ID. Never use a component's own ID here. | | `data-hygraph-field-api-id` | The API ID of the specific field inside the component. Identifies which field to focus within the entry in the editor. Without it, the edit button opens the entry unfocused. | | `data-hygraph-component-chain` | JSON array of `{ fieldApiId, instanceId }` describing the path from root entry to the target field.
  • fieldApiId - The field that contains the nested component.
  • instanceId - Unique identifier of the component instance, as returned in your GraphQL query as described below. This is not the API ID of the component.
| ![Data attributes components markup](/images/docs/developer-guides/click-to-edit-data-attributes-components.png) #### Query to retrieve instanceId Retrieve the `instanceId` using the following query: ```graphql query MyQuery { recipes(where: {id: ""}) { __typename id # This is the entry ID nutrition { id # This is the instanceId for the component chain } ingredients { id # This is the instanceId for the component chain } featuredContent { ... on IngredientSpotlight { id # This is the instanceId for the component chain } } additionalSections { ... on ProTip { id # This is the instanceId for the component chain ... on VideoEmbed { id # This is the instanceId for the component chain } } } } } ``` ```json { "data": { "recipes": [ { "__typename": "Recipe", "id": "cmionbkxj01jm07vpwefnyv1b", "nutrition": { "id": "cmionbkxx01jz07vpqfq1fqu1" }, "ingredients": [ { "id": "cmionbkxw01jn07vplxi3igp7" }, { "id": "cmionbkxw01jp07vpq225ziap" }, { "id": "cmionbkxx01jr07vp3a7qgx9o" }, { "id": "cmionbkxx01jt07vp26pn1h1s" }, { "id": "cmionbkxx01jv07vpcqagyg5b" }, { "id": "cmionbkxx01jx07vpscvzswph" } ], "featuredContent": { "id": "cmiq3y64i0cn207vpxxinofiv" }, "additionalSections": [ { "id": "cmiq3y64s0cn607vp4zlz0q5y" }, { "id": "cmiq3y64s0cn407vpv509s5jq" } ] } ] } } ``` ## Set up the Preview widget in Studio If you have already configured a Preview widget for [live preview](/docs/developer-guides/schema/live-preview), you can skip this step. Click to Edit uses the same widget. 1. Open your Hygraph project. 2. Navigate to **Schema** and select your model. 3. Click the **Sidebar** tab. 4. Select the **Preview** widget from the right sidebar. 5. Complete the **Preview name** and the **URL template** fields, and click **Add**. ## Verify the setup After completing all setup steps, confirm Click to Edit is working end to end. 1. Open an entry in Studio for the model you configured live preview. 2. In the right sidebar, under **Preview**, click **Open live preview**. The preview should load alongside the entry form. 3. Hover over an element you tagged with `data-hygraph-*` attributes. An **Edit** button should appear. 4. Click **Edit**. Studio should scroll to and focus the corresponding field in the entry form. 5. Edit the field value and click **Save & Preview**. The preview should refresh and show the updated content. If the **Edit** button does not appear at step 3, add `debug={true}` to your `PreviewWrapper` component and check the browser console for attribute warnings. Common causes are listed in [Troubleshooting](#troubleshooting) below. ## Known limitation Click to Edit cannot navigate to a field in a specific locale. The **Edit** button opens the correct field in the default locale, but the editor needs to select the field for the target locale manually within Studio. ## Troubleshooting ### Edit buttons do not appear - Confirm `data-hygraph-entry-id` is present on the element. - Add `debug={true}` to your `PreviewWrapper` component and check the browser console. - Verify `NEXT_PUBLIC_HYGRAPH_ENDPOINT` is set correctly. ### Preview does not open Vercel sets an `X-Frame-Options` response header that blocks iframe loading. In your Vercel project, go to **Settings > Deployment Protection** and disable **Vercel Authentication**. ### Preview does not refresh after saving - Confirm the `onSave` callback calls your framework's refresh method, for example, `router.refresh()` in Next.js. - Verify your Permanent Auth Token has **DRAFT** set as the default content stage. - Confirm your GraphQL queries request the `DRAFT` stage in preview mode. ### Real-time field updates not working Real-time updates are disabled by default. Add `sync={{ fieldUpdate: true }}` to your `PreviewWrapper` component to enable them. ### Components do not focus correctly - Check that `data-hygraph-entry-id` is always the root entry ID, not the component instance ID. - Verify the `instanceId` values in your component chain match the `id` fields returned by your GraphQL query. - Confirm the component chain array is ordered from outermost to innermost component. ## What's next - [Click to Edit - Next.js App Router](/docs/developer-guides/schema/click-to-edit-next-js-app-router): Complete implementation for Next.js App Router with code examples for simple fields, basic, nested, and modular components. - [Click to Edit - Next.js Pages Router](/docs/developer-guides/schema/click-to-edit-next-js-pages-router): Complete implementation for Next.js Pages Router with code examples for simple fields, basic, nested, and modular components. - [Click to Edit - Remix](/docs/developer-guides/schema/click-to-edit-remix): Complete implementation for Remix with code examples for simple fields, basic, nested, and modular components. - [Click to Edit - Vue / Nuxt](/docs/developer-guides/schema/click-to-edit-vue-nuxt): Complete implementation for Vue and Nuxt, with shared attribute examples for simple fields, basic, nested, and modular components. - [Click to Edit - Vanilla JavaScript](/docs/developer-guides/schema/click-to-edit-vanilla-js): Complete implementation for vanilla JavaScript with code examples for simple fields, basic, nested, and modular components. - [Click to Edit - Advanced API](/docs/developer-guides/schema/click-to-edit-advanced-api): React hooks, Preview methods, DOM events, and helpers for dynamic content. --- # Click to Edit - Advanced API Source: https://hygraph.com/docs/developer-guides/schema/click-to-edit-advanced-api This page covers advanced Preview SDK APIs beyond the standard [Click to Edit setup](/docs/developer-guides/schema/click-to-edit). Use it when you need custom save handling, connection status, dynamic DOM updates, or lower-level control than `PreviewWrapper` provides. For framework walkthroughs, see: - [Next.js App Router](/docs/developer-guides/schema/click-to-edit-next-js-app-router) - [Next.js Pages Router](/docs/developer-guides/schema/click-to-edit-next-js-pages-router) - [Remix](/docs/developer-guides/schema/click-to-edit-remix) - [Vue / Nuxt](/docs/developer-guides/schema/click-to-edit-vue-nuxt) - [Vanilla JavaScript](/docs/developer-guides/schema/click-to-edit-vanilla-js) ## Additional configuration These options extend the [configuration properties](/docs/developer-guides/schema/click-to-edit#configuration-properties) on the parent setup page. | Property | Required | Description | |---|---|---| | `overlayEnabled` | Optional | Set to `false` to disable hover overlays and **Edit** buttons while keeping the Studio connection active. Defaults to `true`. Available on both `HygraphPreview` and the core `Preview` constructor. | ```tsx router.refresh()} > {children} ``` ## React hooks Import hooks from `@hygraph/preview-sdk/react`. They must run inside a tree wrapped by `HygraphPreview`. ### `usePreview` Returns the Preview instance and connection state. ```tsx import { usePreview } from '@hygraph/preview-sdk/react'; function PreviewStatus() { const { preview, isReady, isConnected } = usePreview(); return (

Ready: {String(isReady)} · Connected: {String(isConnected)} · Mode:{' '} {preview?.getMode() ?? 'n/a'}

); } ``` ### `usePreviewSave` Subscribes to save events. Use this instead of (or in addition to) the `onSave` prop when save handling lives in a child component. ```tsx import { usePreviewSave } from '@hygraph/preview-sdk/react'; import { useRouter } from 'next/navigation'; function SaveListener() { const router = useRouter(); usePreviewSave((entryId) => { console.log('Saved entry:', entryId); router.refresh(); }); return null; } ``` ### `usePreviewEvent` Subscribes to any [DOM event](#dom-events) the SDK dispatches on `document`. ```tsx import { usePreviewEvent } from '@hygraph/preview-sdk/react'; function FieldClickListener() { usePreviewEvent('preview:field-click', (event) => { console.log('Field clicked:', event.detail); }); return null; } ``` ### Other React hooks | Hook | Description | |---|---| | `usePreviewRefresh` | Returns a framework-aware `refresh()` helper. Falls back to `window.location.reload()` when no framework integration is detected. | | `usePreviewRemix` | Subscribes to save events and revalidates with the Remix revalidator when available. | | `usePreviewFieldUpdates` | Callbacks for `preview:field-updated` and `preview:update-failed` when `sync.fieldUpdate` is enabled. | | `usePreviewConnection` | Returns `{ isConnected, isReady, mode }`. | | `usePreviewActions` | Returns `{ refresh, destroy, getVersion, getMode }` for manual control. | | `usePreviewDebug` | Returns registry stats and framework detection for development. | ### `HygraphPreviewNextjs` Optional Next.js helper that wires `refresh` for you. Most apps use `HygraphPreview` with `next/dynamic` and `onSave={() => router.refresh()}` instead, as shown in the [App Router guide](/docs/developer-guides/schema/click-to-edit-next-js-app-router). ```tsx import { HygraphPreviewNextjs } from '@hygraph/preview-sdk/react'; import { useRouter } from 'next/navigation'; export function PreviewWrapper({ children }: { children: React.ReactNode }) { const router = useRouter(); return ( {children} ); } ``` ## Core `Preview` methods When you initialize the SDK with `new Preview()` (vanilla JavaScript, Vue, Nuxt), use these methods on the instance. | Method | Description | |---|---| | `subscribe('save', { callback })` | Listens for Studio save events. Returns an unsubscribe function. | | `refresh()` | Re-scans the DOM for `data-hygraph-*` attributes. Call after you inject or replace HTML without a full page reload. | | `getMode()` | Returns `'iframe'` or `'standalone'`. | | `isConnected()` | Returns whether the SDK is connected to Studio. | | `getVersion()` | Returns the SDK version string. | | `configureOverlay(config)` | Updates overlay styles at runtime. | | `destroy()` | Tears down listeners, overlays, and the Studio connection. Call on page unload in SPAs. | | `getFrameworkIntegration()` | Returns the SDK's framework integration helper used for framework-aware refresh. | | `getFieldRegistryStats()` | Returns counts and diagnostics for registered preview fields. Useful when debugging missing overlays. | | `getFieldRegistryKeys()` | Returns the registry keys for currently tracked fields. | ```js import { Preview } from '@hygraph/preview-sdk/core'; const preview = new Preview({ endpoint: process.env.HYGRAPH_ENDPOINT, studioUrl: process.env.HYGRAPH_STUDIO_URL, debug: true, }); const unsubscribe = preview.subscribe('save', { callback: (entryId) => { console.log('Saved:', entryId); window.location.reload(); }, }); window.addEventListener('beforeunload', () => { unsubscribe(); preview.destroy(); }); ``` ### Dynamic content without a full reload If your app updates the DOM without navigating away, call `refresh()` after new marked-up HTML is in the page: ```js container.innerHTML = `

${entry.title}

`; preview.refresh(); ``` ## Live preview for component arrays When `sync.fieldUpdate` is enabled, the SDK can update component arrays (modular content) in the preview without a full page refresh: - **Reordering** (drag-and-drop) — existing DOM elements move in place - **Deletion** — removed components disappear immediately - **Addition** — new unsaved components are skipped until you save and refresh the page ### Set up a component array container The SDK needs: 1. A **container element** with `data-hygraph-entry-id` and `data-hygraph-field-api-id` pointing to the component array field 2. **Direct children** with `data-hygraph-component-chain` so the SDK can identify each component instance ```tsx import { createPreviewAttributes, createComponentChainLink, } from '@hygraph/preview-sdk/core'; function ArticlePage({ article }) { return (

{article.title}

{/* Container — the SDK targets this for reordering */}
{article.content.map((block) => { const componentChain = [createComponentChainLink('content', block.id)]; return (
); })}
); } ``` ### How it works When Studio detects a structural change (reorder, add, or delete) in a component array, it sends a `COMPONENT_ARRAY` field update. The SDK then: 1. Finds the container via `data-hygraph-entry-id` and `data-hygraph-field-api-id` 2. Reads `data-hygraph-component-chain` from each direct child to map component IDs to DOM elements 3. Reorders existing DOM elements to match the new array order 4. Removes elements for deletions 5. Skips new unsaved components until save and refresh For Rich Text fields rendered as HTML inside components, add `data-hygraph-rich-text-format="html"` so live field updates use the correct format. ### Troubleshooting component arrays - Confirm the **container** has `data-hygraph-entry-id` and `data-hygraph-field-api-id` matching the component array field. - Confirm each direct child has `data-hygraph-component-chain` with the component instance ID. - Enable `debug={true}` (or `debug: true`) to see `[ContentUpdater] COMPONENT_ARRAY` logs. - New unsaved components appear only after saving and refreshing. Reordering and deletion of existing components work immediately when `sync.fieldUpdate` is enabled. ## DOM events The SDK dispatches these events on `document`. Listen with `addEventListener` or `usePreviewEvent`. | Event | `event.detail` | When it fires | |---|---|---| | `preview:ready` | `{ preview }` | SDK finished initializing. | | `preview:connected` | `{ studioOrigin }` | Connected to Studio in iframe mode. | | `preview:disconnected` | `{}` | Connection to Studio ended. | | `preview:content-saved` | `{ entryId, timestamp }` | Studio reported a save. | | `preview:field-click` | `{ entryId, fieldApiId?, locale?, componentChain?, mode? }` | Editor clicked an **Edit** overlay. | | `preview:field-focus` | `{ entryId, fieldApiId, locale? }` | Studio requested field focus sync. | | `preview:field-updated` | `{ entryId, fieldApiId, newValue }` | A live field update applied (`sync.fieldUpdate: true`). | | `preview:update-failed` | `{ entryId, fieldApiId, error }` | A live field update failed. | ```js document.addEventListener('preview:ready', () => { console.log('Preview SDK ready'); }); document.addEventListener('preview:content-saved', (event) => { console.log('Content saved:', event.detail.entryId); }); document.addEventListener('preview:field-click', (event) => { console.log('Field clicked:', event.detail); }); ``` ## Attribute helpers These helpers from `@hygraph/preview-sdk/core` are used throughout the framework guides. The parent [attribute reference](/docs/developer-guides/schema/click-to-edit#add-data-attributes-to-content-elements) covers the HTML attributes they produce. | Helper | Description | |---|---| | `createPreviewAttributes({ entryId, fieldApiId?, locale?, componentChain? })` | Returns an object of `data-hygraph-*` attributes for JSX or `v-bind`. | | `createComponentChainLink(fieldApiId, instanceId)` | Builds one `{ fieldApiId, instanceId }` link for nested components. | | `withFieldPath(attributes, fieldPath)` | Adds `data-hygraph-field-path` (for example, `ingredients.0.quantity`) for debugging nested fields. It does not change Studio focus behavior. | ```tsx import { createPreviewAttributes, createComponentChainLink, withFieldPath, } from '@hygraph/preview-sdk/core'; const chain = [createComponentChainLink('ingredients', ingredient.id)]; const quantityAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'quantity', componentChain: chain, }), `ingredients.${index}.quantity` ); ``` `createPreviewAttributes` also accepts an optional `locale`, which sets `data-hygraph-field-locale`. Click to Edit still cannot jump to a non-default locale in Studio; see the [known limitation](/docs/developer-guides/schema/click-to-edit#known-limitation). ## What's next - [Click to Edit setup](/docs/developer-guides/schema/click-to-edit): Install steps, attribute reference, Studio widget, and troubleshooting. - [Click to Edit - Vanilla JavaScript](/docs/developer-guides/schema/click-to-edit-vanilla-js): UMD setup and server-rendered attribute examples. - Preview SDK on GitHub: Source, changelog, and runnable examples. --- # Click to Edit - Next.js App Router Source: https://hygraph.com/docs/developer-guides/schema/click-to-edit-next-js-app-router This page walks through implementing Click to Edit in a Next.js App Router project using the [Hygraph Preview SDK](https://github.com/hygraph/preview-sdk). By the end, editors can click any instrumented element in the preview to jump directly to that field in Studio. For the general setup overview and configuration reference, see [Click to Edit setup](/docs/developer-guides/schema/click-to-edit). Examples for other supported frameworks are available at: - [Next.js Pages Router](/docs/developer-guides/schema/click-to-edit-next-js-pages-router) - [Remix](/docs/developer-guides/schema/click-to-edit-remix) - [Vue / Nuxt](/docs/developer-guides/schema/click-to-edit-vue-nuxt) - [Vanilla JavaScript](/docs/developer-guides/schema/click-to-edit-vanilla-js) - [Advanced API](/docs/developer-guides/schema/click-to-edit-advanced-api) If you are instrumenting a frontend you did not build from scratch, start with the `PreviewWrapper` component and a single simple field. Confirm the Edit button appears and the save refresh works before adding component attributes. Components require additional data from your GraphQL queries. ## Steps 1. [Install the Preview SDK](#install-the-preview-sdk). 2. [Create a PreviewWrapper component to enable the preview functionality](#create-the-previewwrapper-component). 3. [Set environment variables](#set-environment-variables). 4. [Add data attributes to your content elements](#add-data-attributes-to-content-elements). 5. [Set up the Preview widget in Studio](/docs/developer-guides/schema/click-to-edit#set-up-the-preview-widget-in-studio). 6. [Verify the setup](#verify-the-setup). ## Install the Preview SDK To install the Preview SDK, run the following command: ```bash npm install @hygraph/preview-sdk ``` ## Create the PreviewWrapper component The PreviewWrapper initializes the SDK and wraps your application content. The `HygraphPreview` component handles iframe and standalone mode detection automatically. ### Step 1: Create PreviewWrapper.tsx Create `components/PreviewWrapper.tsx`: ```ts // components/PreviewWrapper.tsx 'use client'; import { useRouter } from 'next/navigation'; import dynamic from 'next/dynamic'; const HygraphPreview = dynamic( () => import('@hygraph/preview-sdk/react').then(mod => ({ default: mod.HygraphPreview })), { ssr: false } ); export function PreviewWrapper({ children }) { const router = useRouter(); return ( { // Optional: Custom save handler console.log('Content saved:', entryId); router.refresh(); }} overlay={{ // Optional: Customize overlay styling style: { borderColor: '#3b82f6', borderWidth: '2px', }, button: { backgroundColor: '#3b82f6', color: 'white', }, }} sync={{ fieldFocus: true, // Optional: Enable field focus sync from Studio fieldUpdate: false, // Optional: Apply live field updates to Preview }} > {children} ); } ``` ### Step 2: Wrap children in layout.tsx The `PreviewWrapper` must wrap `{children}` at the layout level. In Next.js App Router, `{children}` represents the rendered content of the active route. If the wrapper is absent, the SDK cannot register page content and Click to Edit will not work. Import and apply the `PreviewWrapper` component in `app/layout.tsx`: ```ts // app/layout.tsx import { PreviewWrapper } from '@/components/PreviewWrapper'; export default function RootLayout({ children }) { return ( {children} ); } ``` **Configuration properties** | Property | Required / Optional | Description | |-----------------------|----------------------|-------------| | `endpoint` | **Required** | Hygraph Content API endpoint. To learn how to retrieve the Content API endpoint, see [our docs](/docs/developer-guides/project/api-access#content-api). | | `studioUrl` | Optional (recommended) | Studio base URL. Defaults to `https://app.hygraph.com`. Set this if your Studio runs on a regional or custom domain. | | `debug` | Optional | Enables verbose console logs to diagnose attribute issues. | | `mode` | Optional | Forces a specific mode. Options: `'iframe'` \| `'standalone'` \| `'auto'`. Auto-detection works for most cases. | | `onSave` | Optional | Runs after Hygraph reports a save and receives the entry ID for targeted revalidation. | | `overlay` | Optional | Customize overlay border and button appearance. | | `sync.fieldFocus` | Optional | Synchronizes field focus between Studio and the preview when an editor selects a field. | | `sync.fieldUpdate` | Optional | Updates the preview immediately when field updates happen in Studio. Defaults to `false`. | | `allowedOrigins` | Optional | Extends the list of domains that can host your preview iframe. Example: For shared preview environments (QA, staging), add the base URL here. | ## Set environment variables Add the following to `.env.local` in your project's root directory. If you already have these from live preview setup, skip this step. ```bash # .env.local NEXT_PUBLIC_HYGRAPH_ENDPOINT=https://your-region.cdn.hygraph.com/content/your-project-id/master NEXT_PUBLIC_HYGRAPH_STUDIO_URL=https://your-region.hygraph.com HYGRAPH_TOKEN=your-permanent-auth-token # Optional: Required if your project uses authentication ``` **Content API endpoint**: Find this under **Project Settings > Access > Endpoints > High Performance Content API**. For more information, see our documentation on the [Content API](/docs/developer-guides/project/api-access#content-api). **Hygraph Studio base URL**: Copy from your browser's address bar in Studio. Example: `https://studio-eu-central-1-shared-euc1-02.hygraph.com`. **Permanent Auth Token**: Create under **Project Settings > Access > Permanent Auth Tokens**. Set the default content stage to **DRAFT**. Only required if your project enforces authentication on Content API requests. For more information, see our dedicated documentation on [Permanent Auth Tokens](/docs/developer-guides/project/api-access#permanent-auth-tokens). ## Add data attributes to content elements Data attributes (`data-hygraph-*`) connect your rendered elements to specific Hygraph fields. The SDK reads these attributes and attaches Edit overlays automatically. The same attributes work for variants; no additional instrumentation is required. For the full attribute reference, see [Add data attributes to content elements](/docs/developer-guides/schema/click-to-edit#add-data-attributes-to-content-elements). The examples below use a recipe model. To bootstrap the same Hygraph project used here, follow the [project setup instructions](https://github.com/hygraph/preview-sdk/blob/main/examples/project-setup/README.md). ### Simple fields Add `data-hygraph-entry-id` and `data-hygraph-field-api-id` to any element rendering a Hygraph field value. ```ts // app/recipes/[id]/page.tsx return (
{/* Title */}

{recipe.title}

{/* Description */}
{/* Recipe Meta */}
{recipe.prepTime}
{recipe.cookTime}
{recipe.servings}
{recipe.difficulty}
{/* Hero Image */}
{/* Example image rendering */} {recipe.heroImage?.url && ( {recipe.title} )}
); ``` ### Component fields Components require the `data-hygraph-component-chain` attribute so Studio can navigate to the correct nested field instance. Use the helper functions from `@hygraph/preview-sdk/core`: ```tsx import { createComponentChainLink, createPreviewAttributes, withFieldPath, } from '@hygraph/preview-sdk/core'; ``` The `instanceId` in each chain link is the `id` field of the component instance returned in your GraphQL query. It is not the component type's API ID. Query for `id` on every component you want to instrument. See the GraphQL example in [Basic components](#basic-components) below. #### Basic components These are direct children of the Recipe model, and are not nested inside other components. Each one uses a single-link chain. ```ts // Type: Basic component // Structure: `Recipe → ingredients[] → ingredient fields` // Used for: `quantity`, `unit` fields // Path depth: 1 level (Recipe → Ingredient) // Chain link: 1 // app/recipes/[id]/page.tsx {recipe.ingredients.map((ingredient, index) => { const chain = [createComponentChainLink('ingredients', ingredient.id)]; const basePath = `ingredients.${index}`; const quantityAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'quantity', componentChain: chain, }), `${basePath}.quantity` ); // ... used for quantity, unit fields })} ``` ```ts // Type: Basic component // Structure: `Recipe → recipeSteps[] → step fields` // Used for: `stepNumber`, `title`, `instruction`, `estimatedTime` // Path depth: 1 level (Recipe → Recipe Step) // Chain link: 1 // app/recipes/[id]/page.tsx {recipe.recipeSteps.map((step, index) => { const chain = [createComponentChainLink('recipeSteps', step.id)]; const stepBasePath = `recipeSteps.${index}`; const stepNumberAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'stepNumber', componentChain: chain, }), `${stepBasePath}.stepNumber` ); // ... used for stepNumber, title, instruction, estimatedTime })} ``` GraphQL query to retrieve `instanceId` values for basic components: ```graphql query GetRecipe($id: ID!) { recipe(where: { id: $id }, stage: DRAFT) { id title ingredients { id # instanceId for the ingredient component quantity unit } recipeSteps { id # instanceId for the step component stepNumber instruction { html } } } } ``` #### Nested components Nested components require a multi-link chain, ordered from the outermost to the innermost component. ```ts // Type: Nested component // Structure: `Recipe → recipeSteps[] → equipment[] → equipment fields` // Used for: `name`, `required`, `alternatives`, `notes` fields // Path depth: 2 levels (Recipe → Recipe Step → Equipment) // Chain links: 2 // app/recipes/[id]/page.tsx {step.equipment.map((equip, equipIndex) => { const equipChain = [ createComponentChainLink('recipeSteps', step.id), createComponentChainLink('equipment', equip.id) ]; const equipBasePath = `${stepBasePath}.equipment.${equipIndex}`; // ... used for name, required, alternatives, notes fields })} ``` ```ts // Type: Nested component // Structure: `Recipe → recipeSteps[] → ingredientsUsed[] → ingredient fields` // Used for: `ingredientName`, `preparation`, `timing`, `notes` fields // Path depth: 2 levels (Recipe → Recipe Step → Ingredient Reference) // Chain links: 2 // app/recipes/[id]/page.tsx {step.ingredientsUsed.map((ingred, ingredIndex) => { const ingredChain = [ createComponentChainLink('recipeSteps', step.id), createComponentChainLink('ingredientsUsed', ingred.id) ]; const ingredBasePath = `${stepBasePath}.ingredientsUsed.${ingredIndex}`; // ... used for ingredientName, preparation, timing, notes fields })} ``` ```ts // Type: Nested component // Structure: `Recipe → recipeSteps[] → tips[] → tip fields` // Used for: `icon`, `title`, `content` fields // Path depth: 2 levels (Recipe → Recipe Step → Tip) // Chain links: 2 // app/recipes/[id]/page.tsx {step.tips.map((tip, tipIndex) => { const tipChain = [ createComponentChainLink('recipeSteps', step.id), createComponentChainLink('tips', tip.id) ]; const tipBasePath = `${stepBasePath}.tips.${tipIndex}`; // ... used for icon, title, content fields })} ``` GraphQL query to retrieve `instanceId` values for nested components. Extend your basic component query: ```graphql recipeSteps { id stepNumber instruction { html } equipment { id # instanceId for nested equipment name required } tips { id # instanceId for nested tips title content { html } } } ``` #### Modular components Modular components can be one of several types. Use `__typename` to branch and build the chain per type. The examples below display aliased GraphQL fields such as `section.tipTitle` and `section.tipContent`. Those come from query aliases (`tipTitle: title`, `tipContent: content`). `fieldApiId` must still use the schema API IDs (`title`, `content`). ```ts // Type: Modular component // Structure: `Recipe → featuredContent (ProTip | VideoEmbed | IngredientSpotlight)` // Used for: Single featured content section // Path depth: 1 level // Chain link: 1 // app/recipes/[id]/page.tsx // Chain creation const section = recipe.featuredContent; const chain = [createComponentChainLink('featuredContent', section.id)]; const basePath = 'featuredContent'; // Switch switch (section.__typename) { case 'ProTip': { const proTipIconAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'icon', componentChain: chain, }), `${basePath}.icon` ); const proTipTitleAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'title', componentChain: chain, }), `${basePath}.title` ); const proTipContentAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'content', componentChain: chain, }), `${basePath}.content` ); return (
{section.icon || '⭐'}

{section.tipTitle}

); } case 'VideoEmbed': { const videoTitleAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'title', componentChain: chain, }), `${basePath}.title` ); const videoUrlAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'videoUrl', componentChain: chain, }), `${basePath}.videoUrl` ); const videoDescriptionAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'description', componentChain: chain, }), `${basePath}.description` ); return (

{section.videoTitle}

{/* Video embed logic */}
{section.videoDescription && (
)}
); } case 'IngredientSpotlight': { const spotlightImageAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'image', componentChain: chain, }), `${basePath}.image` ); const spotlightTitleAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'ingredient', componentChain: chain, }), `${basePath}.ingredient` ); const spotlightDescriptionAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'description', componentChain: chain, }), `${basePath}.description` ); const spotlightSubstitutesAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'substitutes', componentChain: chain, }), `${basePath}.substitutes` ); return (
{section.ingredientImage ? ( {section.ingredient?.name ) : (
🌿
)}

Star Ingredient: {section.ingredient?.name || 'Unknown'}

{section.substitutes && ( {section.substitutes} )}
); } default: return null; } ``` ```ts // Type: Modular component // Structure: `Recipe → additionalSections[] (ProTip | VideoEmbed | IngredientSpotlight)[]` // Used for: Multiple additional content sections // Path depth: 1 level // Chain link: 1 // app/recipes/[id]/page.tsx {recipe.additionalSections.map((section, index) => { // Chain creation const chain = [createComponentChainLink('additionalSections', section.id)]; const basePath = `additionalSections.${index}`; // Switch switch (section.__typename) { case 'ProTip': { const proTipListIconAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'icon', componentChain: chain, }), `${basePath}.icon` ); const proTipListTitleAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'title', componentChain: chain, }), `${basePath}.title` ); const proTipListContentAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'content', componentChain: chain, }), `${basePath}.content` ); return (
{section.icon || '💡'}

{section.tipTitle}

); } case 'VideoEmbed': { const listVideoTitleAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'title', componentChain: chain, }), `${basePath}.title` ); const listVideoUrlAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'videoUrl', componentChain: chain, }), `${basePath}.videoUrl` ); const listVideoDescriptionAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'description', componentChain: chain, }), `${basePath}.description` ); return (

{section.videoTitle}

{/* Video embed logic */}
{section.videoDescription && (
)}
); } case 'IngredientSpotlight': { const listSpotlightImageAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'image', componentChain: chain, }), `${basePath}.image` ); const listSpotlightTitleAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'ingredient', componentChain: chain, }), `${basePath}.ingredient` ); const listSpotlightDescriptionAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'description', componentChain: chain, }), `${basePath}.description` ); const listSpotlightSubstitutesAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'substitutes', componentChain: chain, }), `${basePath}.substitutes` ); return (
{section.ingredientImage ? ( {section.ingredient?.name ) : (
🥬
)}

Featured: {section.ingredient?.name || 'Unknown'}

{section.substitutes && ( {section.substitutes} )}
); } default: return null; } })} ``` ### Full example ```ts // app/recipes/[id]/page.tsx import { createComponentChainLink, createPreviewAttributes, withFieldPath } from '@hygraph/preview-sdk/core'; // Basic fields

{recipe.title}

{recipe.prepTime}min
{recipe.categories.map((category) => ( {category.name} ))}
// Basic components // Ingredients {recipe.ingredients.map((ingredient, index) => { const chain = [createComponentChainLink('ingredients', ingredient.id)]; const basePath = `ingredients.${index}`; const quantityAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'quantity', componentChain: chain, }), `${basePath}.quantity` ); const unitAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'unit', componentChain: chain, }), `${basePath}.unit` ); return (
{ingredient.ingredient?.name} {ingredient.quantity} {ingredient.unit}
); })} // Recipe Steps (with nested components) {recipe.recipeSteps.map((step, index) => { const chain = [createComponentChainLink('recipeSteps', step.id)]; const stepBasePath = `recipeSteps.${index}`; const stepNumberAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'stepNumber', componentChain: chain, }), `${stepBasePath}.stepNumber` ); const stepTitleAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'title', componentChain: chain, }), `${stepBasePath}.title` ); const instructionAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'instruction', componentChain: chain, }), `${stepBasePath}.instruction` ); return (
{step.stepNumber} {step.title &&

{step.title}

}
{/* Nested: Equipment within Recipe Steps */} {step.equipment?.map((equip, equipIndex) => { const equipChain = [ createComponentChainLink('recipeSteps', step.id), createComponentChainLink('equipment', equip.id), ]; const equipBasePath = `${stepBasePath}.equipment.${equipIndex}`; const nameAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'name', componentChain: equipChain, }), `${equipBasePath}.name` ); const requiredAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'required', componentChain: equipChain, }), `${equipBasePath}.required` ); return (
{equip.name} {equip.required && Required}
); })} {/* Nested: Ingredients Used within Recipe Steps */} {step.ingredientsUsed?.map((ingred, ingredIndex) => { const ingredChain = [ createComponentChainLink('recipeSteps', step.id), createComponentChainLink('ingredientsUsed', ingred.id), ]; const ingredBasePath = `${stepBasePath}.ingredientsUsed.${ingredIndex}`; const ingredientNameAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'ingredientName', componentChain: ingredChain, }), `${ingredBasePath}.ingredientName` ); return (
{ingred.ingredientName}
); })} {/* Nested: Tips within Recipe Steps */} {step.tips?.map((tip, tipIndex) => { const tipChain = [ createComponentChainLink('recipeSteps', step.id), createComponentChainLink('tips', tip.id), ]; const tipBasePath = `${stepBasePath}.tips.${tipIndex}`; const titleAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'title', componentChain: tipChain, }), `${tipBasePath}.title` ); const contentAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'content', componentChain: tipChain, }), `${tipBasePath}.content` ); return (
{tip.title}
); })}
); })} // Modular components // Featured Content (Single) {recipe.featuredContent && (() => { const section = recipe.featuredContent; const chain = [createComponentChainLink('featuredContent', section.id)]; const basePath = 'featuredContent'; switch (section.__typename) { case 'ProTip': { const iconAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'icon', componentChain: chain, }), `${basePath}.icon` ); const titleAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'title', componentChain: chain, }), `${basePath}.title` ); const contentAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'content', componentChain: chain, }), `${basePath}.content` ); return (
{section.icon}

{section.tipTitle}

); } case 'VideoEmbed': { const titleAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'title', componentChain: chain, }), `${basePath}.title` ); const videoUrlAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'videoUrl', componentChain: chain, }), `${basePath}.videoUrl` ); return (

{section.videoTitle}

Video
); } case 'IngredientSpotlight': { const imageAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'image', componentChain: chain, }), `${basePath}.image` ); const ingredientAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'ingredient', componentChain: chain, }), `${basePath}.ingredient` ); const descriptionAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'description', componentChain: chain, }), `${basePath}.description` ); return (
Image

{section.ingredient?.name}

); } default: return null; } })()} // Additional Sections (Array) {recipe.additionalSections.map((section, index) => { const chain = [createComponentChainLink('additionalSections', section.id)]; const basePath = `additionalSections.${index}`; switch (section.__typename) { case 'ProTip': { const iconAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'icon', componentChain: chain, }), `${basePath}.icon` ); const titleAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'title', componentChain: chain, }), `${basePath}.title` ); const contentAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'content', componentChain: chain, }), `${basePath}.content` ); return (
{section.icon}

{section.tipTitle}

); } case 'VideoEmbed': { const titleAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'title', componentChain: chain, }), `${basePath}.title` ); return (

{section.videoTitle}

); } case 'IngredientSpotlight': { const imageAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'image', componentChain: chain, }), `${basePath}.image` ); const ingredientAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'ingredient', componentChain: chain, }), `${basePath}.ingredient` ); return (
Image

{section.ingredient?.name}

); } default: return null; } })} ``` ## Verify the setup 1. Open an entry in Studio for the model you configured. 2. In the right sidebar, click **Open live preview**. The preview should load alongside the entry form. 3. Hover over an element you tagged with `data-hygraph-*` attributes. An **Edit** button should appear. 4. Click **Edit**. Studio should scroll to and focus the corresponding field in the entry form. 5. Edit the field value and click **Save & Preview**. The preview should refresh and show the updated content. If you are working with variants, no additional setup is required. Clicking a tagged element while a variant is open focuses the field directly in the variant overlay. If the **Edit** button does not appear at step 3, add `debug={true}` to your `HygraphPreview` component and check the browser console for missing attribute warnings. For component fields, confirm your GraphQL query includes the `id` field on each component and that the `instanceId` values in your chain match what the query returns. For more information, see [Troubleshooting](/docs/developer-guides/schema/click-to-edit#troubleshooting). ## Related docs - [Click to Edit setup](/docs/developer-guides/schema/click-to-edit): General setup steps, attribute reference, Studio widget configuration, and troubleshooting. - [Click to Edit - Advanced API](/docs/developer-guides/schema/click-to-edit-advanced-api): React hooks, Preview methods, DOM events, and helpers for dynamic content. - [Live preview setup](/docs/developer-guides/schema/live-preview): Configure the Studio preview iframe before deploying Click to Edit to editors. --- # Click to Edit - Next.js Pages Router Source: https://hygraph.com/docs/developer-guides/schema/click-to-edit-next-js-pages-router This page walks through implementing Click to Edit in a Next.js Pages Router project using the Hygraph Preview SDK. By the end, editors can click any instrumented element in the preview to jump directly to that field in Studio. For the general setup overview and configuration reference, see [Click to Edit setup](/docs/developer-guides/schema/click-to-edit). Examples for other supported frameworks are available at: - [Next.js App Router](/docs/developer-guides/schema/click-to-edit-next-js-app-router) - [Remix](/docs/developer-guides/schema/click-to-edit-remix) - [Vue / Nuxt](/docs/developer-guides/schema/click-to-edit-vue-nuxt) - [Vanilla JavaScript](/docs/developer-guides/schema/click-to-edit-vanilla-js) - [Advanced API](/docs/developer-guides/schema/click-to-edit-advanced-api) If you are instrumenting a frontend you did not build from scratch, start with the `PreviewWrapper` component and a single simple field. Confirm the Edit button appears and the save refresh works before adding component attributes. Components require additional data from your GraphQL queries. ## Steps 1. [Install the Preview SDK](#install-the-preview-sdk). 2. [Create a PreviewWrapper component to enable the preview functionality](#create-the-previewwrapper-component). 3. [Set environment variables](#set-environment-variables). 4. [Add data attributes to your content elements](#add-data-attributes-to-content-elements). 5. [Set up the Preview widget in Studio](/docs/developer-guides/schema/click-to-edit#set-up-the-preview-widget-in-studio). 6. [Verify the setup](#verify-the-setup). ## Install the Preview SDK To install the Preview SDK, run the following command: ```bash npm install @hygraph/preview-sdk ``` ## Create the PreviewWrapper component The PreviewWrapper initializes the SDK and wraps your application content. Pages Router has no server components, so the SDK's React module loads client-side through a manual dynamic `import()` once the component has mounted, rather than through the single `next/dynamic` call used in App Router. ### Step 1: Create PreviewWrapper.tsx Create `components/PreviewWrapper.tsx`: ```ts // components/PreviewWrapper.tsx import { useRouter } from 'next/router'; import { useEffect, useState, useMemo, useLayoutEffect } from 'react'; import type { ComponentType, ReactNode } from 'react'; interface PreviewWrapperProps { children: ReactNode; } export function PreviewWrapper({ children }: PreviewWrapperProps) { const router = useRouter(); const [PreviewComponent, setPreviewComponent] = useState | null>(null); const [isMounted, setIsMounted] = useState(false); // useLayoutEffect ensures React is fully initialized before loading the SDK useLayoutEffect(() => { setIsMounted(true); }, []); useEffect(() => { if (!isMounted) return; import('@hygraph/preview-sdk/react') .then((mod) => { setPreviewComponent(() => mod.HygraphPreview); }) .catch((error) => { console.error('Failed to load Hygraph Preview SDK:', error); }); }, [isMounted]); const handleSave = useMemo(() => { return (entryId: string) => { // Optional: Custom save handler console.log('Content saved:', entryId); router.replace(router.asPath); }; }, [router]); // Don't render the preview until mounted and the SDK component is loaded if (!isMounted || !PreviewComponent) { return <>{children}; } return ( {children} ); } ``` ### Step 2: Register PreviewWrapper in _app.tsx Pages Router has no root layout file. Register `PreviewWrapper` in `pages/_app.tsx` instead, and load it with `next/dynamic` and `ssr: false`. If the wrapper is absent, the SDK cannot register page content and Click to Edit will not work. ```tsx // pages/_app.tsx import type { AppProps } from 'next/app'; import dynamic from 'next/dynamic'; const PreviewWrapper = dynamic( () => import('@/components/PreviewWrapper').then(mod => ({ default: mod.PreviewWrapper })), { ssr: false } ); export default function App({ Component, pageProps }: AppProps) { return ( ); } ``` **Configuration properties** | Property | Required / Optional | Description | |-----------------------|----------------------|-------------| | `endpoint` | **Required** | Hygraph Content API endpoint. To learn how to retrieve the Content API endpoint, see [our docs](/docs/developer-guides/project/api-access#content-api). | | `studioUrl` | Optional (recommended) | Studio base URL. Defaults to `https://app.hygraph.com`. Set this if your Studio runs on a regional or custom domain. | | `debug` | Optional | Enables verbose console logs to diagnose attribute issues. | | `mode` | Optional | Forces a specific mode. Options: `'iframe'` \| `'standalone'` \| `'auto'`. Auto-detection works for most cases. | | `onSave` | Optional | Runs after Hygraph reports a save and receives the entry ID for targeted revalidation. | | `overlay` | Optional | Customize overlay border and button appearance. | | `sync.fieldFocus` | Optional | Synchronizes field focus between Studio and the preview when an editor selects a field. | | `sync.fieldUpdate` | Optional | Updates the preview immediately when field updates happen in Studio. Defaults to `false`. | | `allowedOrigins` | Optional | Extends the list of domains that can host your preview iframe. Example: For shared preview environments (QA, staging), add the base URL here. | ## Set environment variables Add the following to `.env.local` in your project's root directory. If you already have these from live preview setup, skip this step. ```bash # .env.local NEXT_PUBLIC_HYGRAPH_ENDPOINT=https://your-region.cdn.hygraph.com/content/your-project-id/master NEXT_PUBLIC_HYGRAPH_STUDIO_URL=https://your-region.hygraph.com HYGRAPH_TOKEN=your-permanent-auth-token # Optional: Required if your project uses authentication ``` **Content API endpoint**: Find this under **Project Settings > Access > Endpoints > High Performance Content API**. For more information, see our documentation on the [Content API](/docs/developer-guides/project/api-access#content-api). **Hygraph Studio base URL**: Copy from your browser's address bar in Studio. Example: `https://studio-eu-central-1-shared-euc1-02.hygraph.com`. **Permanent Auth Token**: Create under **Project Settings > Access > Permanent Auth Tokens**. Set the default content stage to **DRAFT**. Only required if your project enforces authentication on Content API requests. For more information, see our dedicated documentation on [Permanent Auth Tokens](/docs/developer-guides/project/api-access#permanent-auth-tokens). ## Add data attributes to content elements Data attributes (`data-hygraph-*`) connect your rendered elements to specific Hygraph fields. The SDK reads these attributes and attaches Edit overlays automatically. The same attributes work for variants; no additional instrumentation is required. For the full attribute reference, see [Add data attributes to content elements](/docs/developer-guides/schema/click-to-edit#add-data-attributes-to-content-elements). The examples below use a recipe model. To bootstrap the same Hygraph project used here, follow the project setup instructions. Pages Router fetches the entry with `getServerSideProps` (or `getStaticProps`) and passes it to the page component as a prop. The route `id` comes from `context.params`, not `useParams()`. ```ts // pages/recipes/[id].tsx import { GetServerSideProps } from 'next'; export const getServerSideProps: GetServerSideProps = async (context) => { const { id } = context.params!; const recipe = await getRecipe(id as string); if (!recipe) { return { notFound: true }; } return { props: { recipe } }; }; export default function RecipePage({ recipe }) { // ... rendered below } ``` ### Simple fields Add `data-hygraph-entry-id` and `data-hygraph-field-api-id` to any element rendering a Hygraph field value. ```tsx // pages/recipes/[id].tsx return (
{/* Title */}

{recipe.title}

{/* Description */}
{/* Recipe Meta */}
{recipe.prepTime}
{recipe.cookTime}
{recipe.servings}
{recipe.difficulty}
{/* Hero Image */}
{/* Example image rendering */} {recipe.heroImage?.url && ( {recipe.title} )}
); ``` ### Component fields Components require the `data-hygraph-component-chain` attribute so Studio can navigate to the correct nested field instance. Use the helper functions from `@hygraph/preview-sdk/core`: ```tsx import { createComponentChainLink, createPreviewAttributes, withFieldPath, } from '@hygraph/preview-sdk/core'; ``` The `instanceId` in each chain link is the `id` field of the component instance returned in your GraphQL query. It is not the component type's API ID. Query for `id` on every component you want to instrument. See the GraphQL example in [Basic components](#basic-components) below. #### Basic components These are direct children of the Recipe model, and are not nested inside other components. Each one uses a single-link chain. ```ts // Type: Basic component // Structure: `Recipe → ingredients[] → ingredient fields` // Used for: `quantity`, `unit` fields // Path depth: 1 level (Recipe → Ingredient) // Chain link: 1 // pages/recipes/[id].tsx {recipe.ingredients.map((ingredient, index) => { const chain = [createComponentChainLink('ingredients', ingredient.id)]; const basePath = `ingredients.${index}`; const quantityAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'quantity', componentChain: chain, }), `${basePath}.quantity` ); // ... used for quantity, unit fields })} ``` ```ts // Type: Basic component // Structure: `Recipe → recipeSteps[] → step fields` // Used for: `stepNumber`, `title`, `instruction`, `estimatedTime` // Path depth: 1 level (Recipe → Recipe Step) // Chain link: 1 // pages/recipes/[id].tsx {recipe.recipeSteps.map((step, index) => { const chain = [createComponentChainLink('recipeSteps', step.id)]; const stepBasePath = `recipeSteps.${index}`; const stepNumberAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'stepNumber', componentChain: chain, }), `${stepBasePath}.stepNumber` ); // ... used for stepNumber, title, instruction, estimatedTime })} ``` GraphQL query to retrieve `instanceId` values for basic components: ```graphql query GetRecipe($id: ID!) { recipe(where: { id: $id }, stage: DRAFT) { id title ingredients { id # instanceId for the ingredient component quantity unit } recipeSteps { id # instanceId for the step component stepNumber instruction { html } } } } ``` #### Nested components Nested components require a multi-link chain, ordered from the outermost to the innermost component. ```ts // Type: Nested component // Structure: `Recipe → recipeSteps[] → equipment[] → equipment fields` // Used for: `name`, `required`, `alternatives`, `notes` fields // Path depth: 2 levels (Recipe → Recipe Step → Equipment) // Chain links: 2 // pages/recipes/[id].tsx {step.equipment.map((equip, equipIndex) => { const equipChain = [ createComponentChainLink('recipeSteps', step.id), createComponentChainLink('equipment', equip.id) ]; const equipBasePath = `${stepBasePath}.equipment.${equipIndex}`; // ... used for name, required, alternatives, notes fields })} ``` ```ts // Type: Nested component // Structure: `Recipe → recipeSteps[] → ingredientsUsed[] → ingredient fields` // Used for: `ingredientName`, `preparation`, `timing`, `notes` fields // Path depth: 2 levels (Recipe → Recipe Step → Ingredient Reference) // Chain links: 2 // pages/recipes/[id].tsx {step.ingredientsUsed.map((ingred, ingredIndex) => { const ingredChain = [ createComponentChainLink('recipeSteps', step.id), createComponentChainLink('ingredientsUsed', ingred.id) ]; const ingredBasePath = `${stepBasePath}.ingredientsUsed.${ingredIndex}`; // ... used for ingredientName, preparation, timing, notes fields })} ``` ```ts // Type: Nested component // Structure: `Recipe → recipeSteps[] → tips[] → tip fields` // Used for: `icon`, `title`, `content` fields // Path depth: 2 levels (Recipe → Recipe Step → Tip) // Chain links: 2 // pages/recipes/[id].tsx {step.tips.map((tip, tipIndex) => { const tipChain = [ createComponentChainLink('recipeSteps', step.id), createComponentChainLink('tips', tip.id) ]; const tipBasePath = `${stepBasePath}.tips.${tipIndex}`; // ... used for icon, title, content fields })} ``` GraphQL query to retrieve `instanceId` values for nested components. Extend your basic component query: ```graphql recipeSteps { id stepNumber instruction { html } equipment { id # instanceId for nested equipment name required } tips { id # instanceId for nested tips title content { html } } } ``` #### Modular components Modular components can be one of several types. Use `__typename` to branch and build the chain per type. The examples below display aliased GraphQL fields such as `section.tipTitle` and `section.tipContent`. Those come from query aliases (`tipTitle: title`, `tipContent: content`). `fieldApiId` must still use the schema API IDs (`title`, `content`). ```ts // Type: Modular component // Structure: `Recipe → featuredContent (ProTip | VideoEmbed | IngredientSpotlight)` // Used for: Single featured content section // Path depth: 1 level // Chain link: 1 // pages/recipes/[id].tsx // Chain creation const section = recipe.featuredContent; const chain = [createComponentChainLink('featuredContent', section.id)]; const basePath = 'featuredContent'; // Switch switch (section.__typename) { case 'ProTip': { const proTipIconAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'icon', componentChain: chain, }), `${basePath}.icon` ); const proTipTitleAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'title', componentChain: chain, }), `${basePath}.title` ); const proTipContentAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'content', componentChain: chain, }), `${basePath}.content` ); return (
{section.icon || '⭐'}

{section.tipTitle}

); } case 'VideoEmbed': { const videoTitleAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'title', componentChain: chain, }), `${basePath}.title` ); const videoUrlAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'videoUrl', componentChain: chain, }), `${basePath}.videoUrl` ); const videoDescriptionAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'description', componentChain: chain, }), `${basePath}.description` ); return (

{section.videoTitle}

{/* Video embed logic */}
{section.videoDescription && (
)}
); } case 'IngredientSpotlight': { const spotlightImageAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'image', componentChain: chain, }), `${basePath}.image` ); const spotlightTitleAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'ingredient', componentChain: chain, }), `${basePath}.ingredient` ); const spotlightDescriptionAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'description', componentChain: chain, }), `${basePath}.description` ); const spotlightSubstitutesAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'substitutes', componentChain: chain, }), `${basePath}.substitutes` ); return (
{section.ingredientImage ? ( {section.ingredient?.name ) : (
🌿
)}

Star Ingredient: {section.ingredient?.name || 'Unknown'}

{section.substitutes && ( {section.substitutes} )}
); } default: return null; } ``` ```ts // Type: Modular component // Structure: `Recipe → additionalSections[] (ProTip | VideoEmbed | IngredientSpotlight)[]` // Used for: Multiple additional content sections // Path depth: 1 level // Chain link: 1 // pages/recipes/[id].tsx {recipe.additionalSections.map((section, index) => { // Chain creation const chain = [createComponentChainLink('additionalSections', section.id)]; const basePath = `additionalSections.${index}`; // Switch switch (section.__typename) { case 'ProTip': { const proTipListIconAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'icon', componentChain: chain, }), `${basePath}.icon` ); const proTipListTitleAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'title', componentChain: chain, }), `${basePath}.title` ); const proTipListContentAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'content', componentChain: chain, }), `${basePath}.content` ); return (
{section.icon || '💡'}

{section.tipTitle}

); } case 'VideoEmbed': { const listVideoTitleAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'title', componentChain: chain, }), `${basePath}.title` ); const listVideoUrlAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'videoUrl', componentChain: chain, }), `${basePath}.videoUrl` ); const listVideoDescriptionAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'description', componentChain: chain, }), `${basePath}.description` ); return (

{section.videoTitle}

{/* Video embed logic */}
{section.videoDescription && (
)}
); } case 'IngredientSpotlight': { const listSpotlightImageAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'image', componentChain: chain, }), `${basePath}.image` ); const listSpotlightTitleAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'ingredient', componentChain: chain, }), `${basePath}.ingredient` ); const listSpotlightDescriptionAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'description', componentChain: chain, }), `${basePath}.description` ); const listSpotlightSubstitutesAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'substitutes', componentChain: chain, }), `${basePath}.substitutes` ); return (
{section.ingredientImage ? ( {section.ingredient?.name ) : (
🥬
)}

Featured: {section.ingredient?.name || 'Unknown'}

{section.substitutes && ( {section.substitutes} )}
); } default: return null; } })} ``` ### Full example ```ts // pages/recipes/[id].tsx import { GetServerSideProps } from 'next'; import { createComponentChainLink, createPreviewAttributes, withFieldPath } from '@hygraph/preview-sdk/core'; export const getServerSideProps: GetServerSideProps = async (context) => { const { id } = context.params!; const recipe = await getRecipe(id as string); if (!recipe) { return { notFound: true }; } return { props: { recipe } }; }; export default function RecipePage({ recipe }) { return (
{/* Basic fields */}

{recipe.title}

{recipe.prepTime}min
{recipe.categories.map((category) => ( {category.name} ))}
{/* Basic components */} {/* Ingredients */} {recipe.ingredients.map((ingredient, index) => { const chain = [createComponentChainLink('ingredients', ingredient.id)]; const basePath = `ingredients.${index}`; const quantityAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'quantity', componentChain: chain, }), `${basePath}.quantity` ); const unitAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'unit', componentChain: chain, }), `${basePath}.unit` ); return (
{ingredient.ingredient?.name} {ingredient.quantity} {ingredient.unit}
); })} {/* Recipe Steps (with nested equipment) */} {recipe.recipeSteps.map((step, index) => { const chain = [createComponentChainLink('recipeSteps', step.id)]; const stepBasePath = `recipeSteps.${index}`; const stepNumberAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'stepNumber', componentChain: chain, }), `${stepBasePath}.stepNumber` ); const stepTitleAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'title', componentChain: chain, }), `${stepBasePath}.title` ); const instructionAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'instruction', componentChain: chain, }), `${stepBasePath}.instruction` ); return (
{step.stepNumber} {step.title &&

{step.title}

}
{/* Nested: Equipment within Recipe Steps */} {step.equipment?.map((equip, equipIndex) => { const equipChain = [ createComponentChainLink('recipeSteps', step.id), createComponentChainLink('equipment', equip.id), ]; const equipBasePath = `${stepBasePath}.equipment.${equipIndex}`; const nameAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'name', componentChain: equipChain, }), `${equipBasePath}.name` ); const requiredAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'required', componentChain: equipChain, }), `${equipBasePath}.required` ); return (
{equip.name} {equip.required && Required}
); })}
); })} {/* Modular components */} {/* Featured Content (Single) */} {recipe.featuredContent && (() => { const section = recipe.featuredContent; const chain = [createComponentChainLink('featuredContent', section.id)]; const basePath = 'featuredContent'; switch (section.__typename) { case 'ProTip': { const iconAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'icon', componentChain: chain, }), `${basePath}.icon` ); const titleAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'title', componentChain: chain, }), `${basePath}.title` ); const contentAttributes = withFieldPath( createPreviewAttributes({ entryId: recipe.id, fieldApiId: 'content', componentChain: chain, }), `${basePath}.content` ); return (
{section.icon}

{section.tipTitle}

); } default: return null; } })()}
); } ``` ## Verify the setup 1. Open an entry in Studio for the model you configured. 2. In the right sidebar, click **Open live preview**. The preview should load alongside the entry form. 3. Hover over an element you tagged with `data-hygraph-*` attributes. An **Edit** button should appear. 4. Click **Edit**. Studio should scroll to and focus the corresponding field in the entry form. 5. Edit the field value and click **Save & Preview**. The preview should refresh and show the updated content. If you are working with variants, no additional setup is required. Clicking a tagged element while a variant is open focuses the field directly in the variant overlay. If the **Edit** button does not appear at step 3, add `debug={true}` to your `PreviewComponent` and check the browser console for missing attribute warnings. For component fields, confirm your GraphQL query includes the `id` field on each component and that the `instanceId` values in your chain match what the query returns. For more information, see [Troubleshooting](/docs/developer-guides/schema/click-to-edit#troubleshooting). ## Related docs - [Click to Edit setup](/docs/developer-guides/schema/click-to-edit): General setup steps, attribute reference, Studio widget configuration, and troubleshooting. - [Click to Edit - Advanced API](/docs/developer-guides/schema/click-to-edit-advanced-api): React hooks, Preview methods, DOM events, and helpers for dynamic content. - [Live preview setup](/docs/developer-guides/schema/live-preview): Configure the Studio preview iframe before deploying Click to Edit to editors. --- # Click to Edit - Remix Source: https://hygraph.com/docs/developer-guides/schema/click-to-edit-remix This page walks through implementing Click to Edit in a Remix project using the Hygraph Preview SDK. By the end, editors can click any instrumented element in the preview to jump directly to that field in Studio. For the general setup overview and configuration reference, see [Click to Edit setup](/docs/developer-guides/schema/click-to-edit). Examples for other supported frameworks are available at: - [Next.js App Router](/docs/developer-guides/schema/click-to-edit-next-js-app-router) - [Next.js Pages Router](/docs/developer-guides/schema/click-to-edit-next-js-pages-router) - [Vue / Nuxt](/docs/developer-guides/schema/click-to-edit-vue-nuxt) - [Vanilla JavaScript](/docs/developer-guides/schema/click-to-edit-vanilla-js) - [Advanced API](/docs/developer-guides/schema/click-to-edit-advanced-api) If you are instrumenting a frontend you did not build from scratch, start with the `PreviewWrapper` component and a single simple field. Confirm the Edit button appears and the save refresh works before adding component attributes. Components require additional data from your GraphQL queries. ## Steps 1. [Install the Preview SDK](#install-the-preview-sdk). 2. [Create a PreviewWrapper component to enable the preview functionality](#create-the-previewwrapper-component). 3. [Set environment variables](#set-environment-variables). 4. [Add data attributes to your content elements](#add-data-attributes-to-content-elements). 5. [Set up the Preview widget in Studio](/docs/developer-guides/schema/click-to-edit#set-up-the-preview-widget-in-studio). 6. [Verify the setup](#verify-the-setup). ## Install the Preview SDK To install the Preview SDK, run the following command: ```bash npm install @hygraph/preview-sdk ``` ## Create the PreviewWrapper component The PreviewWrapper initializes the SDK and wraps your application content. Remix does not expose server environment variables to the client automatically, so `endpoint` and `studioUrl` are read from a `window.ENV` object populated by the root route's loader, rather than from `process.env` directly inside the component. ### Step 1: Create PreviewWrapper.tsx Create `app/components/PreviewWrapper.tsx`: ```ts // app/components/PreviewWrapper.tsx import { useRevalidator } from '@remix-run/react'; import { useEffect, useState } from 'react'; import type { ComponentType, ReactNode } from 'react'; interface PreviewWrapperProps { children: ReactNode; } export function PreviewWrapper({ children }: PreviewWrapperProps) { const revalidator = useRevalidator(); const [PreviewComponent, setPreviewComponent] = useState | null>(null); useEffect(() => { if (typeof window === 'undefined') return; import('@hygraph/preview-sdk/react') .then((mod) => setPreviewComponent(() => mod.HygraphPreview)) .catch((error) => { console.error('Failed to load Hygraph Preview SDK:', error); }); }, []); if (!PreviewComponent || typeof window === 'undefined') { return <>{children}; } return ( { // Optional: Custom save handler console.log('Content saved, refreshing...'); revalidator.revalidate(); }} overlay={{ // Optional: Customize overlay styling style: { borderColor: '#3b82f6', borderWidth: '2px', }, button: { backgroundColor: '#3b82f6', color: 'white', }, }} sync={{ fieldFocus: true, // Optional: Enable field focus sync from Studio fieldUpdate: false, // Optional: Apply live field updates to Preview }} > {children} ); } ``` ### Step 2: Expose environment variables and register PreviewWrapper in root.tsx Remix keeps server environment variables server-side by default. Expose the ones the SDK needs to the browser with a root `loader` and an inline script, then wrap `` with `PreviewWrapper` inside ``. ```ts // app/root.tsx import { Links, Meta, Outlet, Scripts, ScrollRestoration, useLoaderData, } from '@remix-run/react'; import type { LoaderFunctionArgs } from '@remix-run/node'; import { json } from '@remix-run/node'; import { PreviewWrapper } from './components/PreviewWrapper'; export async function loader({}: LoaderFunctionArgs) { return json({ ENV: { HYGRAPH_ENDPOINT: process.env.HYGRAPH_ENDPOINT, HYGRAPH_STUDIO_URL: process.env.HYGRAPH_STUDIO_URL, }, }); } export default function App() { const { ENV } = useLoaderData(); return ( ``` Replace `__HYGRAPH_ENDPOINT__` and `__HYGRAPH_STUDIO_URL__` with values interpolated from your server-side configuration, as shown in [Set environment variables](#set-environment-variables) below. If the script tag or the `Preview` initialization is missing from a page, the SDK cannot register that page's content and Click to Edit will not work on it. **Configuration properties** | Property | Required / Optional | Description | |-----------------------|----------------------|-------------| | `endpoint` | **Required** | Hygraph Content API endpoint. To learn how to retrieve the Content API endpoint, see [our docs](/docs/developer-guides/project/api-access#content-api). | | `studioUrl` | Optional (recommended) | Studio base URL. Defaults to `https://app.hygraph.com`. Set this if your Studio runs on a regional or custom domain. | | `debug` | Optional | Enables verbose console logs to diagnose attribute issues. | | `mode` | Optional | Forces a specific mode. Options: `'iframe'` \| `'standalone'` \| `'auto'`. Auto-detection works for most cases. | | `overlayEnabled` | Optional | Set to `false` to disable hover overlays and Edit buttons entirely, while keeping the SDK's Studio connection active. Defaults to `true`. | | `overlay` | Optional | Customize overlay border and button appearance. | | `sync.fieldFocus` | Optional | Synchronizes field focus between Studio and the preview when an editor selects a field. | | `sync.fieldUpdate` | Optional | Updates the preview immediately when field updates happen in Studio. Defaults to `false`. | | `allowedOrigins` | Optional | Extends the list of domains that can host your preview iframe. Example: For shared preview environments (QA, staging), add the base URL here. | The core `Preview` class has no `onSave` constructor option. Use `preview.subscribe('save', { callback })` to react to saves. ## Set environment variables The server reads configuration from Node's `process.env` when it renders each page. Set the following on your host, or in a `.env` file loaded by your process manager: ```bash # .env HYGRAPH_ENDPOINT=https://your-region.cdn.hygraph.com/content/your-project-id/master HYGRAPH_STUDIO_URL=https://your-region.hygraph.com HYGRAPH_TOKEN=your-permanent-auth-token # Optional: Required if your project uses authentication ``` ```js // server.js const CONFIG = { HYGRAPH_ENDPOINT: process.env.HYGRAPH_ENDPOINT, HYGRAPH_STUDIO_URL: process.env.HYGRAPH_STUDIO_URL, HYGRAPH_TOKEN: process.env.HYGRAPH_TOKEN || '', }; ``` Interpolate `CONFIG.HYGRAPH_ENDPOINT` and `CONFIG.HYGRAPH_STUDIO_URL` into the inline ` `; res.send(html); }); ``` ## Verify the setup 1. Open an entry in Studio for the model you configured. 2. In the right sidebar, click **Open live preview**. The preview should load alongside the entry form. 3. Hover over an element you tagged with `data-hygraph-*` attributes. An **Edit** button should appear. 4. Click **Edit**. Studio should scroll to and focus the corresponding field in the entry form. 5. Edit the field value and click **Save & Preview**. The preview should refresh and show the updated content. If you are working with variants, no additional setup is required. Clicking a tagged element while a variant is open focuses the field directly in the variant overlay. If the **Edit** button does not appear at step 3, set `debug: true` on the `Preview` instance and check the browser console for missing attribute warnings. Also confirm `js/preview-sdk.js` loaded successfully in the Network tab. For component fields, confirm your GraphQL query includes the `id` field on each component and that the `instanceId` values in your chain match what the query returns. For more information, see [Troubleshooting](/docs/developer-guides/schema/click-to-edit#troubleshooting). ## Related docs - [Click to Edit setup](/docs/developer-guides/schema/click-to-edit): General setup steps, attribute reference, Studio widget configuration, and troubleshooting. - [Click to Edit - Advanced API](/docs/developer-guides/schema/click-to-edit-advanced-api): React hooks, Preview methods, DOM events, and helpers for dynamic content. - [Live preview setup](/docs/developer-guides/schema/live-preview): Configure the Studio preview iframe before deploying Click to Edit to editors. --- # Click to Edit - Vue / Nuxt Source: https://hygraph.com/docs/developer-guides/schema/click-to-edit-vue-nuxt This page walks through implementing Click to Edit in a Vue or Nuxt project using the Hygraph Preview SDK. By the end, editors can click any instrumented element in the preview to jump directly to that field in Studio. For the general setup overview and configuration reference, see [Click to Edit setup](/docs/developer-guides/schema/click-to-edit). Examples for other supported frameworks are available at: - [Next.js App Router](/docs/developer-guides/schema/click-to-edit-next-js-app-router) - [Next.js Pages Router](/docs/developer-guides/schema/click-to-edit-next-js-pages-router) - [Remix](/docs/developer-guides/schema/click-to-edit-remix) - [Vanilla JavaScript](/docs/developer-guides/schema/click-to-edit-vanilla-js) - [Advanced API](/docs/developer-guides/schema/click-to-edit-advanced-api) If you are instrumenting a frontend you did not build from scratch, start with the `PreviewWrapper` component and a single simple field. Confirm the Edit button appears and the save refresh works before adding component attributes. Components require additional data from your GraphQL queries. ## Steps 1. [Install the Preview SDK](#install-the-preview-sdk). 2. Set up the PreviewWrapper for your framework: - [Vue](#vue) - [Nuxt](#nuxt) 3. [Add data attributes to your content elements](#add-data-attributes-to-content-elements). 4. [Set up the Preview widget in Studio](/docs/developer-guides/schema/click-to-edit#set-up-the-preview-widget-in-studio). 5. [Verify the setup](#verify-the-setup). ## Install the Preview SDK To install the Preview SDK, run the following command: ```bash npm install @hygraph/preview-sdk ``` ## Vue For a client-side Vue 3 app with Vue Router. The PreviewWrapper instantiates the `Preview` class from `@hygraph/preview-sdk/core` inside `onMounted`, and tears it down in `onUnmounted`. There is no framework-specific React-style component for Vue or Nuxt. The wrapper uses the `Preview` class from `@hygraph/preview-sdk/core` directly, through Vue's Composition API. Running Nuxt instead of plain Vue? Use the [Nuxt](#nuxt) section below. ### Step 1: Create PreviewWrapper.vue Create `src/components/PreviewWrapper.vue`: ```vue ``` ### Step 2: Register PreviewWrapper in App.vue Wrap `` with `PreviewWrapper` at the root of your app. If the wrapper is absent, the SDK cannot register page content and Click to Edit will not work. ```vue ``` ### Step 3: Set environment variables Add the following to `.env.local` in your project's root directory. If you already have these from live preview setup, skip this step. Vite exposes client-side variables through the `VITE_` prefix. ```bash # .env.local VITE_HYGRAPH_ENDPOINT=https://your-region.cdn.hygraph.com/content/your-project-id/master VITE_HYGRAPH_STUDIO_URL=https://your-region.hygraph.com HYGRAPH_TOKEN=your-permanent-auth-token # Optional: Required if your project enforces authentication ``` **Content API endpoint**: Find this under **Project Settings > Access > Endpoints > High Performance Content API**. For more information, see our documentation on the [Content API](/docs/developer-guides/project/api-access#content-api). **Hygraph Studio base URL**: Copy from your browser's address bar in Studio. Example: `https://studio-eu-central-1-shared-euc1-02.hygraph.com`. **Permanent Auth Token**: Create under **Project Settings > Access > Permanent Auth Tokens**. Set the default content stage to **DRAFT**. Only required if your project enforces authentication on Content API requests. For more information, see our dedicated documentation on [Permanent Auth Tokens](/docs/developer-guides/project/api-access#permanent-auth-tokens). **Configuration properties** | Property | Required / Optional | Description | |-----------------------|----------------------|-------------| | `endpoint` | **Required** | Hygraph Content API endpoint. To learn how to retrieve the Content API endpoint, see [our docs](/docs/developer-guides/project/api-access#content-api). | | `studioUrl` | Optional (recommended) | Studio base URL. Defaults to `https://app.hygraph.com`. Set this if your Studio runs on a regional or custom domain. | | `debug` | Optional | Enables verbose console logs to diagnose attribute issues. | | `mode` | Optional | Forces a specific mode. Options: `'iframe'` \| `'standalone'` \| `'auto'`. Auto-detection works for most cases. | | `overlay` | Optional | Customize overlay border and button appearance. | | `sync.fieldFocus` | Optional | Synchronizes field focus between Studio and the preview when an editor selects a field. | | `sync.fieldUpdate` | Optional | Updates the preview immediately when field updates happen in Studio. Defaults to `false`. | | `allowedOrigins` | Optional | Extends the list of domains that can host your preview iframe. Example: For shared preview environments (QA, staging), add the base URL here. | The core `Preview` class has no `onSave` constructor option. Use `preview.subscribe('save', { callback })` to react to saves, and `preview.destroy()` to clean up the instance when the wrapper unmounts. For a complete Vue example, see the Vue Preview SDK example. When the wrapper and environment variables are in place, continue with [Add data attributes to content elements](#add-data-attributes-to-content-elements). ## Nuxt Nuxt uses the same `Preview` class from `@hygraph/preview-sdk/core`. The differences from plain Vue are how you expose environment variables (`NUXT_PUBLIC_*` / `useRuntimeConfig()`), where you register the wrapper (`app.vue`), and how you refresh data after a save (`refreshNuxtData()`). The `data-hygraph-*` attributes and component-chain helpers are the same as for Vue. ### Step 1: Create PreviewWrapper.vue Create `components/PreviewWrapper.vue`. Initialize the SDK only on the client inside `onMounted`, and read public runtime config instead of `import.meta.env.VITE_*`. ```vue ``` ### Step 2: Register PreviewWrapper in app.vue Wrap `` with `PreviewWrapper` in `app.vue`. If the wrapper is absent, the SDK cannot register page content and Click to Edit will not work. ```vue ``` Nuxt auto-imports components from `components/`, so you usually do not need a manual import. ### Step 3: Configure runtime config and environment variables Expose only the values the browser needs through `runtimeConfig.public`. Keep Permanent Auth Tokens server-side if you use them for GraphQL queries. ```ts // nuxt.config.ts export default defineNuxtConfig({ runtimeConfig: { // Server-only (optional): used by your GraphQL loaders, not by the Preview SDK hygraphToken: process.env.HYGRAPH_TOKEN, public: { hygraphEndpoint: process.env.NUXT_PUBLIC_HYGRAPH_ENDPOINT, hygraphStudioUrl: process.env.NUXT_PUBLIC_HYGRAPH_STUDIO_URL, }, }, }) ``` Add the following to `.env` in your project's root directory. If you already have these from live preview setup, map the public endpoint and Studio URL into `NUXT_PUBLIC_*` as shown. ```bash # .env NUXT_PUBLIC_HYGRAPH_ENDPOINT=https://your-region.cdn.hygraph.com/content/your-project-id/master NUXT_PUBLIC_HYGRAPH_STUDIO_URL=https://your-region.hygraph.com HYGRAPH_TOKEN=your-permanent-auth-token # Optional: server-only; required if your project enforces authentication ``` **Content API endpoint**: Find this under **Project Settings > Access > Endpoints > High Performance Content API**. For more information, see our documentation on the [Content API](/docs/developer-guides/project/api-access#content-api). **Hygraph Studio base URL**: Copy from your browser's address bar in Studio. Example: `https://studio-eu-central-1-shared-euc1-02.hygraph.com`. **Permanent Auth Token**: Create under **Project Settings > Access > Permanent Auth Tokens**. Set the default content stage to **DRAFT**. Only required if your project enforces authentication on Content API requests. Keep this value out of `runtimeConfig.public`. For more information, see our dedicated documentation on [Permanent Auth Tokens](/docs/developer-guides/project/api-access#permanent-auth-tokens). When the wrapper and environment variables are in place, continue with [Add data attributes to content elements](#add-data-attributes-to-content-elements). ## Add data attributes to content elements Data attributes (`data-hygraph-*`) connect your rendered elements to specific Hygraph fields. The SDK reads these attributes and attaches Edit overlays automatically. The same attributes work for variants; no additional instrumentation is required. For the full attribute reference, see [Add data attributes to content elements](/docs/developer-guides/schema/click-to-edit#add-data-attributes-to-content-elements). The examples below use a recipe model. To bootstrap the same Hygraph project used here, follow the project setup instructions. In Vue, read the route id with `useRoute()` from `vue-router` and fetch the entry inside `onMounted`, re-fetching with a `watch` on the route param. In Nuxt, use the same `useRoute()` composable with file-based routes (for example `pages/recipes/[id].vue`) and prefer `useAsyncData` or `useFetch` for the query. Static attribute values can be written directly; dynamic values need Vue's `:` binding shorthand. ```vue ``` ### Simple fields Add `data-hygraph-entry-id` and `data-hygraph-field-api-id` to any element rendering a Hygraph field value. ```vue ``` ### Component fields Components require the `data-hygraph-component-chain` attribute so Studio can navigate to the correct nested field instance. Use the helper functions from `@hygraph/preview-sdk/core`, imported in ` ``` ## Verify the setup 1. Open an entry in Studio for the model you configured. 2. In the right sidebar, click **Open live preview**. The preview should load alongside the entry form. 3. Hover over an element you tagged with `data-hygraph-*` attributes. An **Edit** button should appear. 4. Click **Edit**. Studio should scroll to and focus the corresponding field in the entry form. 5. Edit the field value and click **Save & Preview**. The preview should refresh and show the updated content. If you are working with variants, no additional setup is required. Clicking a tagged element while a variant is open focuses the field directly in the variant overlay. If the **Edit** button does not appear at step 3, set `debug: true` on the `Preview` instance and check the browser console for missing attribute warnings. For component fields, confirm your GraphQL query includes the `id` field on each component and that the `instanceId` values in your chain match what the query returns. For more information, see [Troubleshooting](/docs/developer-guides/schema/click-to-edit#troubleshooting). ## Related docs - [Click to Edit setup](/docs/developer-guides/schema/click-to-edit): General setup steps, attribute reference, Studio widget configuration, and troubleshooting. - [Click to Edit - Advanced API](/docs/developer-guides/schema/click-to-edit-advanced-api): React hooks, Preview methods, DOM events, and helpers for dynamic content. - [Live preview setup](/docs/developer-guides/schema/live-preview): Configure the Studio preview iframe before deploying Click to Edit to editors. - [Live preview - Frontend implementation](/docs/developer-guides/schema/live-preview-frontend): Serve DRAFT content for Vue, Nuxt, and other frameworks. --- # Command menu Source: https://hygraph.com/docs/developer-guides/schema/command-menu Some schema management tasks require completing a series of similar repeated actions with a lot of confirmation popups. Hygraph Studio allows keyboard navigation through the command menu, which greatly speeds up the process. ## How it works To trigger the popup search bar, simply use `CMD+K` (or `CTRL+K` in Windows). ![Command menu](/images/docs/user-guides/command-menu.png) You can also bring up the command menu by clicking `Search` in the top bar: ![Command menu - Search](/images/docs/user-guides/command-menu-navigate.png) Type in the search bar to filter the list of available actions. When calling the command menu, both the global functions - navigation, global commands - and context-specific commands are available. Navigate the list of suggested actions using the Up/Down arrows on your keyboard, or your mouse-wheel. To confirm your selection, use the Enter key or a mouse click. ## Navigation capabilities Depending on the section of the app you're in, the command menu will prioritize which set of actions to show first. ### Top-level navigation ![Command menu - Top-level navigation](/images/docs/user-guides/command-menu-top-level.png) The **Go-to** section offers shortcuts to: - Schema - Content - Assets - API Playground - Apps - Webhooks - Project settings ### Schema ![Command menu - Schema](/images/docs/user-guides/command-menu-contextual-schema.png) The **Schema** section offers shortcuts to: - System and user-defined models in the Schema builder - User-defined components in the Schema builder - User-defined enumerations in the Schema builder ### Content table ![Command menu - Content table](/images/docs/user-guides/command-menu-contextual-content.png) The **Content table** section offers shortcuts to: - User-defined models in the Content editor - User-defined custom views in the Content editor ## Add actions You can create schema elements as well as content entries directly from the command menu. ### Add schema ![Command menu - Add schema](/images/docs/user-guides/command-menu-add-schema.png) The **Add Schema** section offers shortcuts to create new schema elements. You can click on the options there to add a new model, component, remote source or enumeration. ### Add content ![Command menu - Add content](/images/docs/user-guides/command-menu-add-content.png) The **Add Content** section offers shortcuts to create new content entries for any of the models currently in your Schema. ## Project settings ![Command menu - Project settings](/images/docs/user-guides/command-menu-project-settings.png) The **Project settings** section offers shortcuts to each part of your project settings. ## Recently viewed ![Command menu - Recently viewed](/images/docs/user-guides/command-menu-recently-viewed.png) The **Recently viewed** section offers shortcuts to your recently viewed content entries [Click here to learn more about the recently viewed menu.](/docs/developer-guides/schema/recently-viewed) --- # Components Source: https://hygraph.com/docs/developer-guides/schema/components Components let you define reusable groups of fields once and use them across models and content entries. They help reduce duplication and keep content structures consistent across your schema. ## Key terms | Term | Description | | --------------------- | ----------- | | **Component** | A predefined set of fields reusable across models and entries. It is like a flexible template where you define the fields once, then fill them with different content each time the component is used in an entry. | | **Component instance** | A specific occurrence of a component with its own content inside a content entry. | | **Component field** | A field type in your Hygraph schema that lets you embed components within a model. Can be **basic** or **modular**, and both types support multiple values. | | **Basic component field** | Allows only one component type. | | **Modular component field** | Allows two or more component types. | | **Nested components** | A component that contains one or more child component fields, enabling hierarchical content structures. | **Studio UI limit:** The Studio UI supports linking up to **50 component instances** per entry (for both basic and modular fields). Instances beyond 50 remain fully queryable via the API, but won't appear in the UI. This limit exists because a large number of component instances can slow down queries and increase error risk. ## Why use components Components are useful when you need to: - Reuse the same group of fields across multiple models without redefining them - Reduce schema duplication and maintenance overhead - Keep complex content models easier to manage as they scale This saves time and improves the content editing experience. ## Components vs. references Both components and references support reuse, but they serve different purposes: - **Components** reuse a field structure. Each component instance stores its own unique content within the entry. - **References** reuse existing content entries from another model. **Example: Attaching authors to blog posts** | Approach | How it works | Best for | |--------- |------------- |--------- | | **References** | Create entries in an `Author` model and link them to posts. | A fixed set of known authors who contribute to many posts. | | **Components** | Add an author component instance directly to each post and fill in the fields. | Variable or one-off authors where maintaining separate author profiles isn't needed. | For more detailed guidance, see [Components or references](/docs/developer-guides/schema/components-or-references). ## Add a component to your model Adding a component involves two steps: 1. [Create a component](#create-a-component). 2. [Add a component field to your model](#add-a-component-field-to-your-model). Components currently do not support remote sources or Rich Text embedding. ### Create a component 1. Open the **Schema Builder**. 2. In the left sidebar, locate the **Components** section below your models and click **+ Add**. 3. Enter a name in the **Display name** field. The **API ID** and **Plural API ID** fields are auto-populated, but you can edit them. 4. Click **Create component**. 5. Your new component is empty. Add [fields](/docs/api-reference/schema/field-types) to define its structure. **Example:** An `Address` component might include single-line text fields for address lines and city, and a number field for ZIP code. The number of components you can create depends on your Hygraph plan. See pricing details. ### Add a component field to your model 1. Open the **Schema Builder** and select the model you want to update. 2. From the **Field types** sidebar, select a component field type: - **Basic**: embeds a single component type. - **Modular**: embeds two or more component types. 3. Enter a **Display name**. The **API ID** and **Plural API ID** are auto-populated but editable. Optionally, add a **Description**. 4. Toggle **Allow multiple values** if the field should accept more than one component instance. 5. Assign the components: - **Basic**: Use **Select component** to choose one component. - **Modular**: Use **Select allowed components** to choose multiple components. 6. On the **Validations** tab, optionally mark the field as required. 7. On the **Advanced** tab, optionally enable [conditional visibility](/docs/developer-guides/schema/conditional-fields) to show the component field only when specific conditions are met. This is helpful for schemas with deeply nested structures. 8. Click **Add**. ## Use a component in a content entry After adding a component to your model, open the Content Editor to test it. The video above demonstrates four common configurations: - Basic component field — single value - Basic component field — multiple values - Modular component field — single value - Modular component field — multiple values **Studio UI limit:** The Studio UI supports linking up to **50 component instances** per entry (for both basic and modular fields). Instances beyond 50 remain fully queryable via the API, but won't appear in the UI. This limit exists because a large number of component instances can slow down queries and increase error risk. To add a component instance to an entry: 1. In the **Content Editor**, open the relevant model and click **+ Add entry**. 2. Locate the component field in the entry form. 3. Add a component instance: - **Basic component field**: Click **+ Add component**. - **Modular component field**: Select a component from the dropdown. 4. Fill in the component instance fields. 5. If the field supports multiple instances, you can also: - **Duplicate** an instance: Click the context menu and select **Duplicate**. This copies the instance and its content. - **Copy**: Click the context menu and select **Copy**. This copies the instance and its content. You can then paste the component into the same entry or a different entry. See [Copy and paste component instances](#copy-and-paste-component-instances) for more details. - **Add another instance**: Click **+ Add new component**, or use **Add new item above** / **Add new item below** from the context menu. - **Reorder**: Use the context menu or arrow controls to reorder instances. - **Remove**: Click the context menu and select **Remove**. This deletes the instance. - **Remove all**: Click **Remove all**. This deletes all instances at once. ### Copy and paste component instances You can copy a component instance from one entry and paste it into the same entry or a different entry, as long as the target entry allows the same component type. This also works with locales and nested components. To copy and paste a component instance: 1. In the **Content Editor**, open the entry containing the component instance you want to copy. 2. Open the context menu on the component instance and select **Copy**. 3. Navigate to the target entry. This can be the same entry or a different one. 4. On the target component field, paste the copied component instance at the bottom of the list, or above or below a specific instance. - **At the bottom**: Click **+ Paste component**. - **Above a specific instance**: Click the context menu and select **Paste component above**. - **Below a specific instance**: Click the context menu and select **Paste component below**. The pasted instance will appear with all its content pre-filled. Paste is only available if you've copied a component instance that matches the target component field. If the component instance does not match, the **Paste** option will not appear. ## Nested components Nested components let you add component fields inside other components, enabling hierarchical content structures. **Example:** Create a **Section** component and a **Subsection** component, then nest the subsection inside the section. 1. Open a component you've [already created](/docs/developer-guides/schema/components#create-a-component). 2. From the **Field types** sidebar, add a **Basic** or **Modular** component field to the parent component. 3. Enter the **Display name**. The **API ID** and **Plural API ID** are auto-populated but editable. Optionally, add a **Description**. 4. Assign the child components. Select a single component for a **Basic** component field, or multiple components for a **Modular** component field. - **Important:** Only components you've already created appear in the dropdown. Make sure all required components exist before this step. 5. Click **Add**. The nested component field now appears in the parent component's field list. ### Use nested components in an entry After [setting up a nested component](/docs/developer-guides/schema/components#nested-components) in the Schema Builder, open a new entry and add the nested component as a subsection within the parent component. The maximum supported nesting level is **4**. ![Adding nested components in a content entry](/images/docs/user-guides/components-addsubsection.png) ## Related docs - [API reference for components](/docs/api-reference/schema/components) — How to query components via the API. - [Components or references](/docs/developer-guides/schema/components-or-references) — Guidance on when to use components vs. references. --- # Components or references Source: https://hygraph.com/docs/developer-guides/schema/components-or-references Components and references can both be used to reuse and structure content, but they serve different purposes. This guide explains the key differences, when to choose one over the other, and when to combine them. ## Definitions ![Components](/images/docs/user-guides/components-illustration.png) A **component** is a predefined set of fields reusable across models and content entries. You define the fields once in the schema, then fill them with different content each time the component is used in an entry. ![References](/images/docs/user-guides/reference-illustration.png) A **reference** is a relation between two or more content entries in your project. References let you reuse existing entries by connecting them. Once a relation is configured, editors can also create related content directly from the content editor. ## How to choose Because both features support content reuse, the right choice depends on how your content is structured and how your editors work. The sections below cover the main factors to consider. ### Repeated content **Use components** when you want to reuse a field structure across multiple models. Defining the structure once as a component saves developers from recreating the same fields repeatedly. Each time the component is used in an entry, editors fill it in from scratch. **Use references** when you want to reuse existing content entries rather than a field structure. You set up the related model and create the entries first, then link them to other entries using a reference field. **Example: Attaching authors to blog posts** - With **references**, create author entries in a dedicated `Author` model, then link them to posts. This works well when you have a known set of authors, each with their own profile. - With **components**, add an author component instance directly to each post and fill in the fields each time. This works well when many different people can be authors and maintaining individual author profiles isn't practical. ### Simplified content creation screen Components help keep the content creation screen focused and easier to navigate. Instead of displaying all fields at once, a modular component field lets editors select only the component types relevant to the entry they're creating. This results in a more dynamic and streamlined editing experience. Components also support conditional required fields. For example, a modular component containing different product types can mark certain fields as required only when that product type is selected. This guarantees minimum required information is captured for every entry without cluttering the screen with irrelevant fields. References can produce a similar result when a related model acts as a structured form. Editors fill in the data through an overlay when creating or linking a related entry. However, this approach typically results in more models and can be a less intuitive experience for editors unfamiliar with how relations work. ### Editor experience **References** work best for linking to existing content. The flow is straightforward. Editors search for and attach an existing entry. Components are not a good fit for this use case. However, if editors are expected to create new related entries rather than just link to existing ones, references can become confusing. For example, creating a new author from inside a book entry, through a relation overlay, may not feel intuitive to editors accustomed to more traditional CMS interfaces. In that scenario, components are often the better choice. **Exception:** when a component becomes very large, it can crowd the content editing screen and introduce performance issues. If the content in question is a substantial, standalone piece of information, such as a detailed author profile with many fields, a reference may be preferable, since editors can complete that larger form in an overlay rather than inline. **In summary:** - Use references when linking to something that already exists. - Use components when creating content from scratch, unless the component would be very large, in which case a reference with an overlay may provide a better editing experience. ### Content duplication If your project relies on content duplication, note that not all relation types are duplicated when an entry is copied. Only the following are duplicated: - Two-way references with many-to-many or many-to-one cardinality, where more than one model can be referenced. - Two-way references with many-to-many cardinality, where only one model can be referenced. Components are always duplicated with their parent entry. For a complete breakdown, see [Duplicating content](/docs/developer-guides/content/duplicating-content). ### Conclusion **Use components when:** - Your project reuses the same field structure across multiple models. - Content is created from scratch each time the structure is reused. - You want a simplified, dynamic content creation screen for editors. - Your project relies on content duplication. **Use references when:** - You want to link to content that already exists. - Content is reused unchanged across multiple entries. For example, a shared set of related posts in a blog. - The related content is large enough that completing it in an overlay is preferable to filling it in inline. ## Combining components and references Components and references are not mutually exclusive. You can use them together when you need a consistent field structure that also links to existing content entries. For example, you could create a component containing a title, a slug, and a reference field. Reused across different models, this component could link to related products, new arrivals, products from the same vendor, or books by the same author, while keeping the structure consistent. ## Example use cases **Components:** An e-commerce project that sells multiple product types. Listing all possible product fields on a single edit screen makes it hard to navigate, and those fields cannot be made required universally since not all products share the same attributes. A better approach is a `Product` model with common fields, such as name and description at the model level, and a modular component field for product type. Each component (for example, one for clothing and one for accessories) defines its own set of required fields. Those fields appear only when the relevant product type is selected, keeping the screen clean and ensuring that all published entries include the minimum required information. **References:** An e-commerce project with `Category` and `Product` as separate models, where a product can belong to more than one category. The two models are connected through a two-way reference field, letting editors link products to categories without duplicating data. --- # Conditional fields Source: https://hygraph.com/docs/developer-guides/schema/conditional-fields ## Overview Conditional visibility shows Hygraph's commitment to increasing the productivity of editors and developers who are dealing with complex content. As deeply nested content structures can sometimes challenge editors, conditional visibility allows to show selected fields only when editors truly need them. ## What you can do You can use the conditional visibility option on the configuration of certain fields to make them visible or hidden in the content editor. The main use case for conditional fields is to simplify the content form when a model contains complex nested menu structures, where each item may serve as a link or as another layer of dropdowns. [Check out our example](/docs/developer-guides/schema/conditional-fields#nested-structures-example) on how to use conditional visibility to simplify a content form that uses nested components! ## Conditional visibility You will find the `Conditional visibility` option in your field configuration, in the `Advanced` tab. ![Conditional visibility inside field details](/images/docs/user-guides/conditional-fields/field-visibility-checkbox.png) When you select the checkbox, more options display: ![Conditional visibility options](/images/docs/user-guides/conditional-fields/field-visibility-options.png) The dropdown will allow you to select a [Boolean](/docs/developer-guides/schema/conditional-fields#boolean-field) or an [Enumeration](/docs/developer-guides/schema/conditional-fields#enumeration-field) field from the same model, which will be used to set the condition. Condition setting differs depending on which field type you select from the dropdown. The following sections explain how to set conditions using each type. ### Boolean field If you select a boolean field from the `Conditional visibility` dropdown you will have the option to select `is` or `is not` in the following dropdown, and then `true` or `false` in the last one. Let's look into an example of how this works. Imagine we have a schema for a bookshop, with a model for **Author** and a model for **Product** (books). In the **Author** model, we have a boolean field called **In stock**, followed by a reference field that allows adding related books. The reference field has conditional visibility settings that use the **In stock** boolean and are set to `In stock is true`. This means that the reference field to add books will only display in the content editor if you set the **In stock** boolean to `true`. Here's a video showing you how we set this up: Similarly, you could have a component that displays information about when a book will be in stock and use the same **In stock** boolean to set the condition to `In stock is false`. In this case, if your book is not in stock and so you set the boolean to `false`, the component will display on the content form for your editors to add information about when the product will be available. [Check out our troubleshooting section!](/docs/developer-guides/schema/conditional-fields#troubleshooting) ### Enumeration field If you select an enumeration field from the `Conditional visibility` dropdown you will have the option to select one of the following conditions in combination with one of the enumeration values: | Condition | Description | | --------------------------- | ----------------------------------------------------------------------------------------- | | **`is` + Value** | The field is visible if the enumeration value is selected in the content form. | | **`is not` + Value** | The field is visible if the enumeration value is not selected in the content form. | | **`contains any` + Value** | The field is visible if at least one of the indicated the enumeration values is selected. | | **`contains all` + Value** | The field is visible if all of the indicated the enumeration values are selected. | | **`contains none` + Value** | The field is visible if none of the indicated the enumeration values are selected. | The conditions `contain all` and `contain any` work with enumeration fields that allow multiple values. Let's look into an example of how this works. Imagine we have a schema for a bookshop, with a model for **Author** and a model for **Product** (books). In the **Author** model, we have an enumeration field called **Offers** containing 3 values (Location 1, Location 2, Location 3), followed by a reference field that allows adding related books that are on sale. The reference field has conditional visibility settings that use the **Offers** enumeration and are set to `Offers is Location 2`. This means that the reference field to add books on sale will only display in the content editor if you use the **Offers** dropdown to select the `Location 2` value. Here's a video showing you how we set this up: [Check out our troubleshooting section!](/docs/developer-guides/schema/conditional-fields#troubleshooting) ### Edit the base field If you edit the base field - boolean or enumeration - by changing the selected condition on the content form, the content stored in hidden conditional fields will be deleted upon saving the content entry. This is done in order to ensure consistency of the data. This applies when you change any of the visibility conditions from `TRUE` to `FALSE` or `null` (by clearing the boolean field in the content form). ## Nested structures example The conditional visibility functionality is especially useful for simplifying content forms by only showing sections when the configured conditions are met. Check out the following video to see how conditional visibility does this with nested components: ## Troubleshooting Is your `Conditional visibility` checkbox grayed out? This can help: | Check | Solution | | -------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Is your field required?** | Conditional visibility does not support required fields. **Deselect the `Make field required` checkbox to enable conditional visibility.** | | **Is your field hidden?** | Conditional visibility does not support hidden fields. **Set the `field visibility` to `Read / Write` or `Read only` to enable conditional visibility.** | | **Do you have a boolean or enumeration field in your schema?** | Conditional visibility uses boolean or enumeration fields in the model you're working on. **Add a `Boolean` or `Enumeration` field to your model to enable conditional visibility** | --- # Content Finder Source: https://hygraph.com/docs/developer-guides/schema/content-finder ## Overview Content Finder lets you quickly search across all content in your environment, including text fields in models and nested components. The Content Finder search shows a list of all content entries in your environment that match the search terms, listing the most relevant ones at the top, while searching through Single Line, Multi-Line, Markdown, and RichText fields. ## Using Content Finder 1. Click the icon next to `Search` in the top bar or open the Command Menu with `CMD+K` (or CTRL+K in Windows). 2. Type keywords in the search field and click enter. 3. Select a content entry from the results list to access it in the Content Editor. The results list shows the top-matching content entries at the top, and you can access any of them with a click. If you need to access another content entry that has the specified search term, click `Search` or open the Command Menu to view the search results again. - Content Finder's search index refreshes when you make content or schema changes, ensuring the search results are always up-to-date. - Content Finder supports Single-Line, Multi-Line, Markdown, and Rich Text fields. Content Finder will search all supported fields, not just the title: ![Content Finder search results](/images/docs/user-guides/content-finder/content-finder-search-results.png) It helps editors easily identify the content entry they're looking for. The Command Menu - where you will find the Content Finder feature - keeps your results until you start a new search, so you can open it again and continue looking through your results list. ## Search results & permissions Content Finder shows results depending on the permissions the user has. For instance, if an Editor only has `READ` permissions for two models, the search results will only show those two models. However, conditional permissions are not yet supported. ## Content Finder limitations Content Finder has the following limitations: - Content Finder only supports exact matches. For instance, if you look for “portrait” rather than “portraits”, only results with the word “portrait” in them will display. - Content Finder shows a maximum of 20 results at a time. You need to click Load more to see additional results. - Custom roles with conditional content permissions cannot search through models where [conditions](/docs/getting-started/access-and-permissions/user-roles-and-permissions#using-conditions) apply. They can only search models where they have unconditional read access. System roles (Admins, Developers, Editors, Contributors) can search across all models, as they have full read access to all entries. --- # Set up live preview Source: https://hygraph.com/docs/developer-guides/schema/live-preview Live preview loads your frontend in a side-by-side iframe inside Studio. Editors open any entry, click **Open live preview**, and see exactly how their unpublished changes look on the actual site, without leaving the entry form. The preview refreshes when the editor saves the content entry. It does not update in real time as the editor types. Live preview is not compatible with native mobile applications. Requests made by the preview count against your project's rate limits. Live preview does not require the Hygraph Preview SDK. Configure your frontend to serve DRAFT content first, then add the Preview widget in Studio with that URL template. ## Create a draft preview token Your preview URL needs to serve content from Hygraph's `DRAFT` stage. Create a Permanent Auth Token with `DRAFT` as the default stage for preview, and a second token with `PUBLISHED` as the default stage for production. Your application switches between them based on context. 1. Go to **Project Settings > Access > Permanent Auth Tokens**. 2. Click **Add token** and give it a name, for example, `Preview token`. 3. Under **Default stage for content delivery**, click **Change default stage**, and select **Draft**. 4. Click **Initialize defaults**. Repeat the above steps to create a production token: name it, for example, `Production token`, set the default stage to **Published**, and click **Initialize defaults**. Use this token for live traffic so visitors only receive published content; use the draft preview token only when the preview URL is loaded. ## Choose a preview URL [Implement your frontend](/docs/developer-guides/schema/live-preview-frontend) so it serves `DRAFT` content for the template you will paste into Studio. Pick the row that matches your frontend and use its URL template in the Preview widget: | Frameworks | How it works | URL template | |---|---|---| | React, Vue, Nuxt, Vanilla JavaScript | The frontend reads `?preview=true` and switches the GraphQL stage for that request. | `https://your-domain.com/{slug}?preview=true` | | Next.js | Studio hits a preview route that validates a secret, sets a cookie or session, and redirects to the content page. The content page then queries `DRAFT`. | `https://your-domain.com/api/preview?secret=MY_SECRET_TOKEN&slug={slug}` | | Remix | Studio hits a preview route that validates a secret, sets a cookie or session, and redirects to the content page. The content page then queries `DRAFT`. | `https://your-domain.com/preview?secret=MY_SECRET_TOKEN&slug={slug}` | | Astro | A separate host or environment always serves `DRAFT`. The URL points at the content page directly. | `https://preview.your-domain.com/{slug}` | ## Add a preview widget You need to configure the Preview widget once per content model. Use the URL template from [Choose a preview URL](#choose-a-preview-url) that matches your frontend. 1. In your Hygraph project, click **Schema**. 2. Select the model you want to enable preview for. 3. Click the **Sidebar** tab at the top of the screen. 4. Select the **Preview** widget from the right sidebar. 5. Complete the **Preview name** and **URL template** fields. Paste the template from your frontend setup. See [Define a preview URL template](#define-a-preview-url-template) for how placeholders such as `{slug}` work. 6. Click **Add**. If more than one preview URL is required, for example, a local development URL and a staging URL, click **+ Add** to add additional URLs. Editors select between them using a dropdown in the content entry sidebar. ### Define a preview URL template The URL template tells Studio how to construct the preview link for each entry. It uses your domain plus one or more field values from the content entry as identifiers. Use curly brace notation to insert field values. As soon as you type `{` in the **URL template** field, Studio shows two groups of available tokens: **Available fields**: These are the fields on your model. Default system fields available on every model are `id`, `createdAt`, `updatedAt`, `publishedAt`, and `scheduledIn`. Any custom fields you have added to the model, such as `slug`, also appear here. **Other placeholders**: `{locale}` adds a locale selector to the preview. When your model has more than one locale configured, editors can switch between locales directly in the preview panel. Without it, the switcher does not appear even if your project has multiple locales configured. **Common patterns:** ``` https://preview.your-domain.com/blog/{slug} https://your-domain.com/{slug}?preview=true https://preview.your-domain.com/posts/{id} https://preview.your-domain.com/{locale}/posts/{slug} ``` Use `{slug}` when your frontend routes by slug. Use `{id}` when it routes by entry ID. You can combine multiple fields and placeholders in a single template if your URL structure requires it. Framework-specific templates, including redirect routes with a secret token, are listed in [Choose a preview URL](#choose-a-preview-url). Implementation code for each framework is in [Frontend implementation](/docs/developer-guides/schema/live-preview-frontend). If your preview URL serves the PUBLISHED stage, editors will see changes in the preview only after they publish the content entry. The preview will not show any draft content. See [Create a draft preview token](#create-a-draft-preview-token) and [Choose a preview URL](#choose-a-preview-url). #### Add a secret token For Next.js and Remix, include a secret in the URL that only your application and Hygraph know. This prevents the preview route, for example `https://your-domain.com/api/preview?secret=MY_SECRET_TOKEN&slug={slug}`, from being accessed directly without going through Studio. React, Vue, Nuxt, Vanilla JavaScript, and Astro URL templates do not require this secret. React, Vue, Nuxt, and Vanilla JavaScript templates use `?preview=true` (or similar) with no secret. Anyone who discovers that URL pattern can view unpublished draft content. For sensitive content, add a secret check like the Next.js and Remix examples, or restrict access to your preview deployment. Astro uses a separate preview deployment that always serves drafts. Keep that deployment access-restricted (for example behind authentication or your hosting provider's protection). 1. Generate a secret key using a key generator of your choice. Use a strong random string. 2. Replace `MY_SECRET_TOKEN` in the URL template with the generated secret. 3. Add the secret to your app environment as `HYGRAPH_PREVIEW_SECRET` (for example in `.env.local` for Next.js): ```bash HYGRAPH_PREVIEW_SECRET=MY_SECRET_TOKEN ``` Next.js and Remix use a secure cookie or session for preview. For local development over `http://localhost`, you may need to temporarily allow insecure cookies or test against an HTTPS preview URL so the cookie works inside the Studio iframe. ### Add a Variants preview URL If your model uses [Variants](/docs/developer-guides/schema/variants), you can add one of the variant placeholders, such as `${variant.id}`, `{variant.segments[0].id}`, or `{variant.segments[*].id}`, to your URL template. This lets Studio construct a preview URL that passes the variant or segment ID to your frontend. Editors can preview how content looks for a specific segment before publishing. Without a variant placeholder in the URL template, the variant or segment ID is not passed to the frontend and the preview shows the main entry content only. You can combine variant placeholders with `{locale}` in the same URL template. Use `{id}` for the entry ID, the same as in a standard preview URL. `${variant.id}` uses a `$` prefix; segment placeholders use curly braces only. Ensure you have [added the Preview widget and defined a base URL template](#add-a-preview-widget) before configuring Variants preview. 1. In your Hygraph project, click **Schema** and select the model. 2. Click the **Sidebar** tab. 3. Click **Edit widget** on the Preview widget. 4. Under **Variant preview settings**, complete the **Preview name** and **URL template** fields. 5. Click **Update**. **Supported URL template tokens for Variants:** | Token | Resolves to | |----------------------------|--------------------------------------------------| | `${variant.id}` | The variant ID | | `{variant.segments[0].id}` | The first segment ID | | `{variant.segments[*].id}` | All segment IDs, comma-separated and URL-encoded | **Example URLs and what they resolve to:** | Approach | Template | Example output | |------------------|--------------------------------------------------------------------------------|--------------------------------------------------------------| | By variant ID | `https://preview.your-domain.com/post/{id}?variant=${variant.id}` | `.../post/cmeaacg1t...?variant=cmeaacld1...` | | By first segment | `https://preview.your-domain.com/post/{id}?segment={variant.segments[0].id}` | `.../post/cmeaacg1t...?segment=cmeaafql4...` | | By all segments | `https://preview.your-domain.com/post/{id}?segments={variant.segments[*].id}` | `.../post/cmeaacg1t...?segments=cmeaafql4...%2Ccmeaafn9e...` | `{variant.segments[*].id}` returns all segment IDs as a comma-separated list. Commas are URL-encoded as `%2C`. Your frontend interprets the query parameter and queries the API for the correct variant. ## Verify the setup 1. Open an entry in Studio for the model you configured live preview. 2. In the right sidebar, click **Open live preview**. The preview should load alongside the entry form. 3. Make a change to a field and click **Save & Preview**. The preview should refresh and show your change. If the preview panel is blank or shows an error, see the [Troubleshooting](#troubleshooting) section. ## Edit or delete a preview URL 1. Navigate to the **Schema** builder and select the model. 2. Next to the **Preview** widget, do one of the following: - To edit a preview URL, click the pencil icon, update the **Preview name** or **URL template** fields, and click **Update**. - To delete a preview URL, click the trash icon at the end of the row you want to remove. A preview widget needs to contain at least one URL. If you need to remove the only configured URL, delete the widget from the **Sidebar** tab instead. You can re-add the widget later if needed. **Delete the preview widget** ![Delete a preview widget](/images/docs/user-guides/live-preview/remove-preview-widget.png) 1. Navigate to the **Schema** builder. 2. Select the model that contains the preview widget that you want to delete. 3. Click on the context menu for the widget, and select `Remove`. ## Troubleshooting ### CSP or security header issues Your site's security headers are blocking iframe embedding. Check your response headers in your browser's developer tools: 1. Navigate to the **Network** tab. 2. Search for the page you are trying to see. 3. Under the **Headers** subtab, click the **Response Headers**. 4. Determine whether the security configuration matches one of the options below: - If `X-Frame-Options` is set, remove it from your application. - If `Content-Security-Policy` is set, add `frame-ancestors 'self' https://*.hygraph.com` to the header value. If you are hosting on Vercel, go to **Settings > Deployment Protection** and disable **Vercel Authentication**. This removes the `X-Frame-Options` header. Disabling Vercel Authentication makes preview deployments publicly accessible. ### Stale data Your frontend is serving the `PUBLISHED` stage in preview. Check that: - Your preview token has `DRAFT` set as the default content stage. - Your application is using the preview token, not the production token, when the preview URL is loaded. - Your GraphQL queries are not hardcoding `stage: PUBLISHED`. - You do not have a caching layer active in preview mode. ### No Open live preview button The entry should have been saved at least once, that is, it should exist in the `DRAFT` stage. The button does not appear on unsaved entries. ## What's next - [Click to Edit setup](/docs/developer-guides/schema/click-to-edit): Learn how to install the Hygraph Preview SDK and configure Click to Edit so editors can jump from any preview element directly to the corresponding field in Hygraph Studio. --- # Live preview - Frontend implementation Source: https://hygraph.com/docs/developer-guides/schema/live-preview-frontend Live preview requires your frontend to serve content from the Hygraph `DRAFT` stage when loaded inside Studio's preview iframe. This page covers how to implement that for React, Next.js, Remix, Vue, Nuxt, Astro, and vanilla JavaScript. **Ways to query the DRAFT stage** - Create a URL for your web app that queries the `DRAFT` stage of your endpoint. This could look like: `https://preview.yourwebsite.com`. You could also use a query parameter on the URL, like this: `https://yourwebsite.com?preview=true`. - Create a Permanent Auth Token that returns the `DRAFT` stage for the preview environment. - Change from `PUBLISHED` to `DRAFT` in your app based on a preview context inside your frontend. ## Prerequisites - A Permanent Auth Token that returns the `DRAFT` stage for the preview environment. Create one in [Set up live preview](/docs/developer-guides/schema/live-preview#create-a-draft-preview-token). - Your High Performance Content API endpoint. - Find it under **Project Settings > Access > Endpoints > High Performance Content API** in your Hygraph project. URL templates to paste into the Studio Preview widget are listed in [Choose a preview URL](/docs/developer-guides/schema/live-preview#choose-a-preview-url). Each framework section below includes the matching template. ## Next.js Next.js uses the `draftMode` API from `next/headers` to toggle between `DRAFT` and `PUBLISHED` content. A preview route sets the draft mode cookie; your page components read `isEnabled` to decide which stage to query. Use this URL template in Studio: `https://your-domain.com/api/preview?secret=MY_SECRET_TOKEN&slug={slug}`. In Next.js 13 and later, the `draftMode` cookie is set with `SameSite=Lax` by default, which prevents it from working inside an iframe. The route handler below applies a workaround that sets `SameSite=None` after enabling draft mode. The cookie is also marked `secure`, so local preview over `http://localhost` may not persist the cookie inside the Studio iframe. Prefer an HTTPS preview URL for iframe testing. ### Step 1: Create the preview route handler Create `app/api/preview/route.ts`. This route validates the secret token and slug, enables draft mode, and redirects to the correct page. ```ts // app/api/preview/route.ts import { draftMode, cookies } from 'next/headers'; import { redirect } from 'next/navigation'; export async function GET(request: Request) { const { searchParams } = new URL(request.url); const secret = searchParams.get('secret'); const slug = searchParams.get('slug'); // Check the secret against the value configured in your environment if (secret !== process.env.HYGRAPH_PREVIEW_SECRET || !slug) { return new Response('Invalid token', { status: 401 }); } // Fetch the slug from Hygraph to ensure we don't run into redirect loops const res = await fetch(process.env.HYGRAPH_ENDPOINT!, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${process.env.HYGRAPH_PREVIEW_TOKEN}`, }, body: JSON.stringify({ query: ` query SinglePage($slug: String!) { page(where: { slug: $slug }, stage: DRAFT) { slug } } `, variables: { slug }, }), }); const { data } = await res.json(); if (!data?.page) { return new Response('Invalid slug', { status: 401 }); } // Workaround for https://github.com/vercel/next.js/issues/49927 // Enable draft mode as usual (await required in Next.js 15+) const draft = await draftMode(); draft.enable(); // Update the cookie and set SameSite=None so it works inside the Hygraph iframe const cookieStore = await cookies(); const cookie = cookieStore.get('__prerender_bypass'); cookieStore.set({ name: '__prerender_bypass', value: cookie?.value, httpOnly: true, path: '/', secure: true, sameSite: 'none', }); redirect(`/${data.page.slug}`); } ``` ### Step 2: Query DRAFT or PUBLISHED based on draft mode In your page component, read `isEnabled` from `draftMode()` to switch between stages. ```ts // app/[slug]/page.tsx import { request } from 'graphql-request'; import { draftMode } from 'next/headers'; export default async function Page({ params, }: { params: Promise<{ slug: string }>; }) { // isEnabled is true if the draft mode cookie has been set by the preview route // See: https://nextjs.org/docs/app/building-your-application/configuring/draft-mode const { slug } = await params; const { isEnabled } = await draftMode(); // Default stage is PUBLISHED. Switch to DRAFT when draft mode is active. const query = ` query Page($slug: String!, $stage: Stage! = PUBLISHED) { page(where: { slug: $slug }, stage: $stage) { title description slug } } `; const variables = { stage: isEnabled ? 'DRAFT' : 'PUBLISHED', slug, }; // Use the preview token in draft mode so the DRAFT stage is accessible, // and the production token otherwise so only PUBLISHED content is served const endpoint = process.env.HYGRAPH_ENDPOINT!; const token = isEnabled ? process.env.HYGRAPH_PREVIEW_TOKEN! : process.env.HYGRAPH_PRODUCTION_TOKEN!; const { page } = await request(endpoint, query, variables, { Authorization: `Bearer ${token}`, }); return (

{page.title}

{page.description}

); } ``` ### Step 3: Configure Next.js environment variables ```bash # .env.local HYGRAPH_ENDPOINT=https://your-region.cdn.hygraph.com/content/your-project-id/master HYGRAPH_PREVIEW_TOKEN=your-draft-stage-token HYGRAPH_PRODUCTION_TOKEN=your-published-stage-token HYGRAPH_PREVIEW_SECRET=MY_SECRET_TOKEN ``` For a full implementation including more complex content models, see the SKNCRE Cosmetics Shop Starter. ## React For a client-side React app, such as Vite, Create React App, with no server of its own, route the preview request through a serverless function instead of calling Hygraph directly from the browser. This keeps your Permanent Auth Token off the client. Use this URL template in Studio: `https://your-domain.com/{slug}?preview=true`. Anyone who discovers this URL pattern can view your draft content. For sensitive content, add a secret check like the Next.js or Remix examples do, or restrict access to your preview deployment. ### Step 1: Create a React serverless preview function Deploy a serverless function (for example, a Vercel or Netlify function) that picks the stage and token based on a `preview` flag, then queries Hygraph. Do not prefix `HYGRAPH_PREVIEW_TOKEN` or `HYGRAPH_PRODUCTION_TOKEN` with `VITE_`, `REACT_APP_`, or any build-tool prefix that exposes variables to the browser bundle. Only the serverless function needs them. ```js // api/preview.js export default async function handler(req, res) { const { slug, preview } = req.query; const isPreview = preview === 'true'; const stage = isPreview ? 'DRAFT' : 'PUBLISHED'; const token = isPreview ? process.env.HYGRAPH_PREVIEW_TOKEN : process.env.HYGRAPH_PRODUCTION_TOKEN; const query = ` query Page($slug: String!, $stage: Stage!) { page(where: { slug: $slug }, stage: $stage) { title description slug } } `; const response = await fetch(process.env.HYGRAPH_ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, }, body: JSON.stringify({ query, variables: { slug, stage } }), }); const { data } = await response.json(); res.status(200).json(data.page); } ``` ### Step 2: Call the proxy from your React page Read the `preview` query parameter and pass it through to your function. The component never touches the Hygraph endpoint or token directly. ```jsx // src/Page.jsx import { useEffect, useState } from 'react'; import { useParams, useSearchParams } from 'react-router-dom'; export default function Page() { const { slug } = useParams(); const [searchParams] = useSearchParams(); const isPreview = searchParams.get('preview') === 'true'; const [page, setPage] = useState(null); useEffect(() => { fetch(`/api/preview?slug=${slug}&preview=${isPreview}`) .then((res) => res.json()) .then(setPage); }, [slug, isPreview]); if (!page) return null; return (

{page.title}

{page.description}

); } ``` ### Step 3: Configure React environment variables Set these on the serverless function's host, not in a client-exposed `.env` file. ```bash # Serverless function environment HYGRAPH_ENDPOINT=https://your-region.cdn.hygraph.com/content/your-project-id/master HYGRAPH_PREVIEW_TOKEN=your-draft-stage-token HYGRAPH_PRODUCTION_TOKEN=your-published-stage-token ``` For click-to-edit overlays on top of this preview flow, see [Click to Edit setup](/docs/developer-guides/schema/click-to-edit). ## Remix Remix runs loaders on the server, so you can use a cookie-backed session to persist preview state across requests, the same way the Next.js route handler above uses a cookie. Use this URL template in Studio: `https://your-domain.com/preview?secret=MY_SECRET_TOKEN&slug={slug}`. The session cookie uses `sameSite: 'none'` and `secure: true` so it works inside the Studio iframe. Local preview over `http://localhost` may not persist that cookie; prefer an HTTPS preview URL for iframe testing. ### Step 1: Create a session for preview state ```ts // app/sessions.server.ts import { createCookieSessionStorage } from '@remix-run/node'; export const { getSession, commitSession } = createCookieSessionStorage({ cookie: { name: '__preview_mode', secrets: [process.env.HYGRAPH_PREVIEW_SECRET!], secure: true, sameSite: 'none', // required so the cookie works inside the Studio preview iframe path: '/', httpOnly: true, }, }); ``` ### Step 2: Create the preview resource route This route validates a secret token, sets the session, and redirects to the page. ```ts // app/routes/preview.tsx import { redirect, type LoaderFunctionArgs } from '@remix-run/node'; import { getSession, commitSession } from '~/sessions.server'; export async function loader({ request }: LoaderFunctionArgs) { const url = new URL(request.url); const slug = url.searchParams.get('slug'); const secret = url.searchParams.get('secret'); if (!slug || secret !== process.env.HYGRAPH_PREVIEW_SECRET) { throw new Response('Invalid token', { status: 401 }); } const session = await getSession(request.headers.get('Cookie')); session.set('isPreview', true); return redirect(`/${slug}`, { headers: { 'Set-Cookie': await commitSession(session) }, }); } ``` ### Step 3: Query DRAFT or PUBLISHED in the page loader ```ts // app/routes/$slug.tsx import { json, type LoaderFunctionArgs } from '@remix-run/node'; import { useLoaderData } from '@remix-run/react'; import { getSession } from '~/sessions.server'; export async function loader({ request, params }: LoaderFunctionArgs) { const session = await getSession(request.headers.get('Cookie')); const isPreview = session.get('isPreview') === true; const stage = isPreview ? 'DRAFT' : 'PUBLISHED'; const token = isPreview ? process.env.HYGRAPH_PREVIEW_TOKEN : process.env.HYGRAPH_PRODUCTION_TOKEN; const query = ` query Page($slug: String!, $stage: Stage!) { page(where: { slug: $slug }, stage: $stage) { title description slug } } `; const res = await fetch(process.env.HYGRAPH_ENDPOINT!, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, }, body: JSON.stringify({ query, variables: { slug: params.slug, stage } }), }); const { data } = await res.json(); return json({ page: data.page }); } export default function Slug() { const { page } = useLoaderData(); return (

{page.title}

{page.description}

); } ``` ### Step 4: Configure Remix environment variables ```bash HYGRAPH_ENDPOINT=https://your-region.cdn.hygraph.com/content/your-project-id/master HYGRAPH_PREVIEW_TOKEN=your-draft-stage-token HYGRAPH_PRODUCTION_TOKEN=your-published-stage-token HYGRAPH_PREVIEW_SECRET=your-secret-token ``` For click-to-edit overlays on top of this preview flow, see [Click to Edit - Remix](/docs/developer-guides/schema/click-to-edit-remix). ## Vue For a client-side Vue 3 app with Vue Router (no Nuxt), route the preview request through a serverless function instead of calling Hygraph directly from the browser. This keeps your Permanent Auth Token off the client. Use this URL template in Studio: `https://your-domain.com/{slug}?preview=true`. Anyone who discovers this URL pattern can view your draft content. For sensitive content, add a secret check like the Next.js or Remix examples do, or restrict access to your preview deployment. ### Step 1: Create a Vue serverless preview function Deploy a serverless function (for example, a Vercel or Netlify function) that picks the stage and token based on a `preview` flag, then queries Hygraph. Do not prefix `HYGRAPH_PREVIEW_TOKEN` or `HYGRAPH_PRODUCTION_TOKEN` with `VITE_` or any build-tool prefix that exposes variables to the browser bundle. Only the serverless function needs them. ```js // api/preview.js export default async function handler(req, res) { const { slug, preview } = req.query; const isPreview = preview === 'true'; const stage = isPreview ? 'DRAFT' : 'PUBLISHED'; const token = isPreview ? process.env.HYGRAPH_PREVIEW_TOKEN : process.env.HYGRAPH_PRODUCTION_TOKEN; const query = ` query Page($slug: String!, $stage: Stage!) { page(where: { slug: $slug }, stage: $stage) { title description slug } } `; const response = await fetch(process.env.HYGRAPH_ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, }, body: JSON.stringify({ query, variables: { slug, stage } }), }); const { data } = await response.json(); res.status(200).json(data.page); } ``` ### Step 2: Call the proxy from your Vue page Read the `preview` query parameter and pass it through to your function. The component never touches the Hygraph endpoint or token directly. ```vue ``` ### Step 3: Configure Vue environment variables Set these on the serverless function's host, not in a client-exposed `.env` file. ```bash # Serverless function environment HYGRAPH_ENDPOINT=https://your-region.cdn.hygraph.com/content/your-project-id/master HYGRAPH_PREVIEW_TOKEN=your-draft-stage-token HYGRAPH_PRODUCTION_TOKEN=your-published-stage-token ``` Running Nuxt instead of plain Vue? Use the [Nuxt](#nuxt) section below. It queries Hygraph server-side, so it doesn't need the proxy function. For click-to-edit overlays on top of this preview flow, see [Click to Edit - Vue / Nuxt](/docs/developer-guides/schema/click-to-edit-vue-nuxt). ## Nuxt Nuxt uses a plugin to detect a `?preview=true` query parameter and expose a global `$preview` variable. Keep Hygraph tokens in server-only runtime config, and query Hygraph from a server API route so client-side navigation still works. Use this URL template in Studio: `https://your-domain.com/{slug}?preview=true`. Anyone who discovers this URL pattern can view your draft content. For sensitive content, add a secret check like the Next.js or Remix examples do, or restrict access to your preview deployment. ### Step 1: Create the Nuxt preview plugin Create `/plugins/preview.ts`. Nuxt reads this automatically on startup. ```ts // plugins/preview.ts export default defineNuxtPlugin((nuxtApp) => { const route = useRoute(); // Refresh page data when navigating while ?preview=true is present nuxtApp.hook('page:finish', () => { if (route.query.preview === 'true') { refreshNuxtData(); } }); // Expose a reactive $preview flag for templates and page logic return { provide: { preview: computed(() => route.query.preview === 'true'), }, }; }); ``` The plugin refreshes page data after in-app navigation in preview mode. `$preview` stays in sync with the current route query. For more ways to configure Nuxt preview mode, see the Nuxt usePreviewMode documentation. ### Step 2: Configure Nuxt runtime config Keep Hygraph tokens server-side. Add them to private `runtimeConfig` in `nuxt.config.ts`. Only server routes can read these values and they are empty in the browser. ```ts // nuxt.config.ts export default defineNuxtConfig({ runtimeConfig: { hygraphEndpoint: process.env.HYGRAPH_ENDPOINT, hygraphPreviewToken: process.env.HYGRAPH_PREVIEW_TOKEN, hygraphProductionToken: process.env.HYGRAPH_PRODUCTION_TOKEN, }, }); ``` ### Step 3: Create a Nuxt server API route Move the Hygraph query into `server/api/` so tokens stay on the server during SSR and client-side navigation. ```ts // server/api/page.get.ts export default defineEventHandler(async (event) => { const query = getQuery(event); const slug = String(query.slug ?? ''); const isPreview = query.preview === 'true'; const config = useRuntimeConfig(); const stage = isPreview ? 'DRAFT' : 'PUBLISHED'; const token = isPreview ? config.hygraphPreviewToken : config.hygraphProductionToken; const graphqlQuery = ` query Page($slug: String!, $stage: Stage! = PUBLISHED) { page(where: { slug: $slug }, stage: $stage) { title description } } `; const response = await $fetch<{ data: { page: { title: string; description: string } } }>( config.hygraphEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, }, body: { query: graphqlQuery, variables: { slug, stage }, }, } ); return response.data.page; }); ``` ### Step 4: Call the API route from the Nuxt page Use `useFetch` so the page works on first load and on in-app navigation without exposing tokens to the browser. ```vue ``` ### Step 5: Configure Nuxt environment variables ```bash HYGRAPH_ENDPOINT=https://your-region.cdn.hygraph.com/content/your-project-id/master HYGRAPH_PREVIEW_TOKEN=your-draft-stage-token HYGRAPH_PRODUCTION_TOKEN=your-published-stage-token ``` For a full Nuxt implementation, see the SKNCRE Cosmetics Shop Starter. ## Astro Astro is focused on Static Site Generation (SSG) and due to its incredible flexibility in terms of using frontend frameworks, Astro does not have a built-in preview mode like Next.js or Nuxt. Static generation caches responses, which means editors will always see stale content in preview. You can configure the Astro site to run in SSR (server-side rendered) mode and use an environment variable to switch the query stage. Use this URL template in Studio: `https://preview.your-domain.com/{slug}`. This setup always serves draft content on the preview host. Keep the preview deployment access-restricted (for example behind authentication or your hosting provider's protection). ### Step 1: Enable SSR in the Astro config ```js // astro.config.mjs import { defineConfig } from 'astro/config'; import vercel from '@astrojs/vercel/serverless'; export default defineConfig({ // SSR is required — static generation caches responses and editors // will always see stale content in preview without it output: 'server', adapter: vercel(), // or node, netlify, cloudflare }); ``` Other adapters (`node`, `netlify`, `cloudflare`) are supported. The key requirement is `output: 'server'`. ### Step 2: Read the Astro preview environment variable in pages ```astro --- // src/pages/[slug].astro import { request } from 'graphql-request'; // HYGRAPH_PREVIEW=true is set in your preview deployment environment only. // Do not set this in production — it would serve DRAFT content to all visitors. const isPreview = import.meta.env.HYGRAPH_PREVIEW === 'true'; // Default stage is PUBLISHED. Switch to DRAFT in the preview deployment. const query = ` query Page($slug: String!, $stage: Stage! = PUBLISHED) { page(where: { slug: $slug }, stage: $stage) { title description } } `; const variables = { stage: isPreview ? 'DRAFT' : 'PUBLISHED', slug: Astro.params.slug, }; const token = isPreview ? import.meta.env.HYGRAPH_PREVIEW_TOKEN : import.meta.env.HYGRAPH_PRODUCTION_TOKEN; const { page } = await request( import.meta.env.HYGRAPH_ENDPOINT, query, variables, { Authorization: `Bearer ${token}` } ); --- {page.title}

{page.title}

{page.description}

``` ### Step 3: Configure Astro environment variables Set `HYGRAPH_PREVIEW=true` in your preview environment, such as Vercel or Netlify. Do not set it in your production environment. The `true` value matches the `preview=true` convention used by the other frameworks. ```bash # Preview environment only HYGRAPH_PREVIEW=true HYGRAPH_ENDPOINT=https://your-region.cdn.hygraph.com/content/your-project-id/master HYGRAPH_PREVIEW_TOKEN=your-draft-stage-token ``` ```bash # Production environment HYGRAPH_PREVIEW=false HYGRAPH_ENDPOINT=https://your-region.cdn.hygraph.com/content/your-project-id/master HYGRAPH_PRODUCTION_TOKEN=your-published-stage-token ``` For a full Astro implementation, see the SKNCRE Cosmetics Shop Starter. That starter may still show an older `HYGRAPH_IS_PREVIEW` name; use `HYGRAPH_PREVIEW=true` as shown above for consistency with the other frameworks. For live Studio preview after save, use SSR as shown above rather than static generation. ## Vanilla JavaScript For frontend applications without a framework, run a minimal Node.js server that reads the preview signal from the request and switches the GraphQL query stage before rendering HTML. Keep the Permanent Auth Token server-side only, since a token embedded in client-side JavaScript is visible in DevTools to anyone who opens it. Treat it like a secret and do not expose it to the client. Use this URL template in Studio: `https://your-domain.com/{slug}?preview=true`. Anyone who discovers this URL pattern can view your draft content. For sensitive content, add a secret check like the Next.js or Remix examples do, or restrict access to your preview deployment. ### Step 1: Read the preview query parameter and switch stage Check for `?preview=true` on the incoming request, then pick the stage and token before querying Hygraph. ```js // server.js import express from 'express'; import fetch from 'node-fetch'; const app = express(); async function fetchPage(slug, isPreview) { const stage = isPreview ? 'DRAFT' : 'PUBLISHED'; const token = isPreview ? process.env.HYGRAPH_PREVIEW_TOKEN : process.env.HYGRAPH_PRODUCTION_TOKEN; const query = ` query Page($slug: String!, $stage: Stage!) { page(where: { slug: $slug }, stage: $stage) { title description slug } } `; const res = await fetch(process.env.HYGRAPH_ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, }, body: JSON.stringify({ query, variables: { slug, stage } }), }); const { data } = await res.json(); return data.page; } app.get('/:slug', async (req, res) => { const isPreview = req.query.preview === 'true'; const page = await fetchPage(req.params.slug, isPreview); res.send(`

${page.title}

${page.description}

`); }); app.listen(3000); ``` ### Step 2: Configure Vanilla JavaScript environment variables ```bash HYGRAPH_ENDPOINT=https://your-region.cdn.hygraph.com/content/your-project-id/master HYGRAPH_PREVIEW_TOKEN=your-draft-stage-token HYGRAPH_PRODUCTION_TOKEN=your-published-stage-token ``` The route reads `preview` on each request and queries `DRAFT` only for that request. Visitors without the parameter get `PUBLISHED` content. For a full implementation including the Hygraph Preview SDK for click-to-edit overlays, see [Click to Edit - Vanilla JavaScript](/docs/developer-guides/schema/click-to-edit-vanilla-js). ## What's next - [Live preview](/docs/developer-guides/schema/live-preview): Configure live preview so editors can see draft content rendered in your frontend before publishing. - [Get started with Click to Edit](/docs/developer-guides/schema/click-to-edit): Add SDK-powered edit buttons on top of a live preview. --- # Recently viewed Source: https://hygraph.com/docs/developer-guides/schema/recently-viewed ## Overview Editors often work in projects with a lot of content views and content entries. Hygraph offers a way to quickly go back to content entries that they recently accessed or edited. This feature increases editor productivity and saves time by providing shortcuts to recently viewed content. It is designed to make finding content easier, preventing Editors from having rely on memory or search through different content views to find a recently accessed content entry. ## Access You can access a menu with shortcuts to your recently viewed content entries by clicking on the clock icon in the top bar. ![Recently viewed menu access](/images/docs/user-guides/recently-viewed-access.png) This information is also available in the recently viewed section of the [command menu](/docs/developer-guides/schema/command-menu#recently-viewed). To find this section, press `CMD+K` (or `CTRL+K` in Windows), and type “Recently” in the search. If you are in the Content editor when using the command menu, this section will be at the top of the list. ## Usage Simply click on one of the entries to open it in the content editor. Optionally, you can use your keyboard to select an entry and press enter. The content form of the selected entry will open in the same tab. ![Recently viewed menu](/images/docs/user-guides/recently-viewed-menu.png) The list of up to 30 content entries will display sorted by recency, with the most recent entries showing at the top. It shows all recently viewed entries including the ones that were opened from inside a content form, such as an `Author` entry opened from the `Book` entry. Please note: - The list only shows the entries viewed by the current user. - It is project & environment specific: Users can only see entries in the project and in the environment that they're currently viewing. - Users can see recently viewed entries **dating back up to one month**. ## Limitations ### Search There is no search available in the recently viewed menu. However, if you check the recently viewed section of the [command menu](/docs/developer-guides/schema/command-menu#recently-viewed), you can to use the search there to find a content entry. ### Inaccessible entry If a user tries to open an inaccessible entry (due to removal or permission loss), an error message displays. - **Entry removal:** The user sees a message indicating that the entry cannot be found. - **Permission loss:** The user sees a message indicating that they do not have permission to view the entry. --- # References Source: https://hygraph.com/docs/developer-guides/schema/references Reference fields connect content entries across models. Choosing the wrong direction or cardinality requires deleting and rebuilding the field, so plan your schema before you start. ## Add a reference field 1. Go to **Schema** and select the model you want to update. 2. From the **Field types** sidebar, select **Reference**. 3. On the [**Define**](#define-tab) tab, set the reference type, model to reference, reference direction, and relation cardinality. These settings cannot be changed after saving. 4. On the [**Configure**](#configure-tab) tab, set the display name, API ID, and any behavior options. 5. If you selected a two-way reference, configure the reverse field on the [**Reverse field**](#reverse-field-tab) tab. 6. Click **Add**. The sections below describe each tab and its options in detail. ## Define tab The **Define** tab controls the structural decisions that cannot be changed after the field is saved. Reference type, model to reference, reference direction, and relation cardinality are all locked after you click **Add**. For Union Type fields, you can edit which models are included in the relation after the initial save, but you cannot change the other settings. Delete and recreate the field to change any other locked setting. ### Reference type Use the **Reference type** radio buttons to control how many models this field can point to. - **Allow only one model to be referenced**: The field returns content from one model. One-way or two-way reference direction is available. - **Allow multiple models to be referenced (Union Type)**: The field returns content from multiple models. Only two-way reference direction is available; one-way is disabled. ### Model to reference Select the models this field will reference. The **Relation cardinality** controls appear below after you make a selection. ### Reference direction - **One-way reference**: The field is queryable only from the model where you add it. The referenced model has no reverse field. - For example, a **Post** referencing an **Author** one-way means editors can select an author from the `Post` entry, but opening an `Author` entry shows no list of connected posts. - **Two-way reference**: Hygraph adds a reverse field to the referenced model, making the relation queryable from both sides. - For example, a **Post** referencing an **Author** two-way means editors can see connected posts from the `Author` entry as well. This also adds the **Reverse field** tab to the modal. ### Relation cardinality Relation cardinality controls how many entries can be connected on each side of the relation. Use the dropdown in the center of the diagram to set this. The checkboxes below the diagram reflect your selection and update automatically. **One-way reference** | Option | Meaning | Example | |--------|---------|---------| | One to one | One entry in this model links to exactly one entry in the referenced model | A country links to one capital city | | One to many | One entry in this model links to multiple entries in the referenced model | An author links to multiple posts | **Two-way reference** | Option | Meaning | Example | |--------|---------|---------| | One to one | One entry on each side links to exactly one entry on the other side | Each country has one capital city and each capital city belongs to only one country | | One to many | One entry in this model links to multiple entries in the referenced model | An author links to multiple posts, and each post belongs to only one author | | Many to one | Multiple entries in this model link to one entry in the referenced model | Multiple reviews link to one product, and each review belongs to only one product | | Many to many | Multiple entries on both sides can be connected | A product belongs to many categories and a category contains many products | ## Configure tab The **Configure** tab sets the field's display name, API ID, and behavior options. These settings can be updated after the field is created. ### Display name and API ID Hygraph pre-populates **Display name** and **API ID** based on your Define tab selections. You can change them before saving. ### Description **Description** is optional. The text you enter here displays as a hint for content editors and API users. ### Validation The validation option available depends on the cardinality you selected on the **Define** tab. These validations apply in the UI only and are intended to help editors remember to connect entries. Keep the following in mind: - Unlike other [required fields](/docs/api-reference/schema/field-configuration#make-field-required), the API does not enforce these validations. Content can be saved or published via the API regardless of validation state. - If a referenced entry is deleted, or disconnected from the other side of a two-way reference, Hygraph removes the link automatically but does not display an error. For example, if an Article requires a linked Product and someone deletes that Product, the Article loses the connection silently with no error shown. - If an editor removes a connected entry from the content form, the warning appears and blocks publishing until the requirement is met. - An entry that exists as a draft but has not been published still counts as connected.
One to one and Many to one
**Require a connected entry** displays an editorial warning in the content form if no entry is linked. Unlike other [required fields](/docs/api-reference/schema/field-configuration#make-field-required), the API does not enforce this requirement. You can optionally add a **Custom error message** to replace the default warning text.
One to many and Many to many
**Limit connected entry count** specifies a minimum and/or maximum number of connected entries upon saving or publishing the entry. Use the dropdown to set the constraint type: | Option | Inputs required | |--------|----------------| | Between | Min and Max | | At least | Min only | | Not more than | Max only | You can optionally add a **Custom error message** to replace the default validation text.
### Enable variants **Enable variants** lets this reference field hold different values for each Variant of a content entry. Editors can set a different connected entry per Variant without affecting the main entry or other Variants. This option is not available for unique fields. Disabling it after content has been created removes the field and its values from all Variants. Variants are an enterprise feature; see the [Variants documentation](/docs/developer-guides/schema/variants) for setup details. ### Show based on condition **Show based on condition** lets you configure conditional visibility so the field only appears in the content form when a specified condition is met. Select an Enumeration or Boolean field from this model to establish the condition. ### Field visibility **Field visibility** controls how the field appears in the UI and API. **Read / Write** is selected by default. | Option | Behavior | |--------|----------| | Read / Write | Field can be read and edited | | Read only | Field is shown but cannot be edited in the UI; editable through the API only | | Hidden | Field is not shown in the UI; other fields can still reference it | | API only | Field is not shown in the UI; readable and editable through the API only | If you selected a one-way reference, click **Add** to save the field. If you selected a two-way reference, continue to the **Reverse field** tab. ## Reverse field tab The **Reverse field** tab appears only for two-way references. It configures the field that Hygraph adds to the referenced model. Hygraph pre-populates **Display name** and **API ID** from the referenced model name. You can change them before saving. **Field visibility** defaults to **API only** for the reverse field. Click **Add** to add the field to the model. To make further configuration changes to the reverse field after saving, navigate to the referenced model and edit the field. A banner displays which model the reverse field belongs to, for example, "Field in **Article** referencing **Product**". Click **Visit model** to open that model directly. ## What's next - [Fetch relations](/docs/api-reference/content-api/queries#fetching-relations): Learn how to query connected entries via the Content API. - [Create references](/docs/editor-guides/content/create-references): Learn how to connect entries to one another directly from the content editor. - [Duplicate content](/docs/developer-guides/content/duplicating-content): See how relational field types behave when content entries are duplicated. - [Components or references](/docs/developer-guides/schema/components-or-references): Understand when to use a component versus a reference for related content. --- # Reserved terms Source: https://hygraph.com/docs/developer-guides/schema/reserved-terms This document contains a list of reserved terms, organized by category. Attempting to use these terms will result in a warning, informing you that you must use a different word instead. ![Reserved term warning in the UI](/images/docs/user-guides/reserved-terms-errormessage.png) ## GraphQL type names - int - boolean - date - datetime - enum - float - fragment - input - interface - json - mutation - node - query - scalar - string - subscription - union ## Reserved non system type names - Color - ColorInput - Entity - HEX - Id - Locale - Location - LocationInput - RGBA - RGBAHue - RGBATransparency - RichText - RichTextAST - ScheduledOperation - ScheduledRelease - Search - Status - User ## Reserved non system field names - createdat - createdby - documentInStages - history - id - locale - localizations - publish - publishedat - publishedby - status - updatedat - updatedby - versions --- # Working with the slug field Source: https://hygraph.com/docs/developer-guides/schema/slug-field ## Overview A slug field is a field type used to create parts of a URL that identify a page with a human readable slug, which is both user & search engine friendly. The resulting slugs are concise and contain relevant keywords. ![Slug example](/images/docs/user-guides/slug-example.png) Typically, a slug consists of lowercase letters, numbers, and hyphens, with spaces replaced by hyphens or simply removed. ## What you can do You can configure an initial value for slugs using the slug field type. This field is of type `String` in your GraphQL schema but provides a powerful UI feature that comes in the form of automatic initial value generation. You can configure the slug field to be generated from other field values in your model. ## Create a slug field 1. Navigate to the Schema builder and select the model you want to add a slug field to. 2. Select `Slug` from the **Add field** sidebar. 3. Add a `Display name`, which will autocomplete the `API ID`. You can optionally add a `Description`. 4. Under **Slug options**, specify whether this field should be lowercase, and enable whether you want this field to be generated from a template. 5. If you opted to generate from template, enter a template for your slug. 6. Configure any additional settings for the model. By default, the field will be set to unique, and a specific matching pattern will be provided. 7. Click `Add` to save. ## Slug templates Each Slug field template can be generated using the **Available fields** outlined above the input. If your model has a `title`, or `name` field, you could specify the slug template as `{title}`, or `{name}` respectively. ## Using the slug field When creating content, the slug field will be automatically generated from the template field you're using. For example, if your slug field is based off a `title` field, when you start typing into the `title` field, it will also update the slug field. You can override the slug field value by typing in the box. You can also "Regenerate" the value to fallback to the template you provided. --- # Taxonomies Source: https://hygraph.com/docs/developer-guides/schema/taxonomies ## Overview A taxonomy is a group of terms arranged in a hierarchical structure. The primary purpose of a taxonomy is to classify and logically connect related content items using a more elaborate structure than [enumerations](/docs/developer-guides/schema/using-enumerations). This helps with content management and improves user experience with enhanced searchability. Taxonomies are an enterprise feature. Contact our sales team for access. ## How it works 1. The developer or the administrator defines the taxonomy in the schema using hierarchical tags. - Example: `Clothes → Men → Pants → Formal` 2. The developer or the administrator adds the taxonomy to a model. 3. While creating content based on the model, the content team classifies content using one of the predefined terms in the taxonomy. ## Benefits of taxonomies - Your end users can find and filter content easily. - Your content is well-organized and well-structured in your content management system. - You can dynamically classify content for search, filtering, and personalization purposes. For example, a user could search for full-sleeve formal shirts or full-sleeve shirts. If the user is looking for a full-sleeve shirt, they would be shown both formal and casual shirts. Depending on the parent-child relationships you’ve set up, your options to dynamically classify content are limitless. ## Permissions Here, are the permissions given to the default roles in Hygraph: | Role | Permissions | |-----------------------------|-----------------------------------------------------| | Admin |
  • Create, read, update and delete a taxonomy.
  • Create, read, update and delete taxonomy nodes.
| | Developer |
  • Create, read, and update a taxonomy.
  • Create, read, update and delete taxonomy nodes.
| | Editor |
  • Read a taxonomy.
  • Create, read, and update taxonomy nodes.
| | Contributor |
  • Read a taxonomy.
  • Read taxonomy nodes.
| ## Create a taxonomy 1. Navigate to the **Schema** builder. 2. In the Schema list, click **+ Add** next to Taxonomies. Or, if you're adding a taxonomy for the first time in the project, click **Taxonomies**, and then click the **+ Add Taxonomy** button located at the center of the screen. The **New Taxonomy** screen is displayed. 3. Provide a **Display name**. This is the name of the taxonomy that is displayed in Hygraph. The **API ID** field is autogenerated as you enter the **Display name**, but you can edit this value. The API ID is used to access this taxonomy through the API. 4. Optionally, you can add a **Description**. 5. Click **Add Taxonomy**. Now, the **Nodes** tab is displayed. This tab displays the parent-child relationships defined in this taxonomy. 6. The `Display name` of the taxonomy is available as the root node name. To modify this, hover over the root node, click **...** (context menu), and then click **Rename node**. 7. To add a child node to the root node, hover over the root node, and click **Add child node**. Provide a name, and click **Add**. The `API ID` is autogenerated as you enter the name. This ID is used to access the taxonomy node through the API. - You cannot have duplicate node or API IDs in the same taxonomy. Repeat this step to create multiple parent-child relationships between the different nodes. ## Add a taxonomy to a model 1. Navigate to the **Schema** builder. 2. Select the model you wish to add the taxonomy to. 3. From the **Add fields** right sidebar, select the **Taxonomy** field. 4. Complete the `Display name` field. This autogenerates the `API ID` field, but you can edit this value if you want. 5. Use the `Taxonomy` dropdown to select the taxonomy you want to add to this model. 6. Optionally, you can also add a `Description`. 7. This screen allows you to control different properties of your **Taxonomy** field: you can allow multiple values by selecting the checkbox under the `Field options` section. 8. Click `Add` to save. After you add a taxonomy to your model, it is available for use in the Content Editor. ## Use demo taxonomy 1. Navigate to the **Schema** builder. 2. If you're adding a taxonomy for the first time in the project, click **Taxonomies**, and then click the **Add demo taxonomy** button located at the center of the screen. 3. The system generates the demo taxonomy and displays the taxonomy nodes. ## Modify a taxonomy 1. Navigate to the **Schema** builder. 2. Under **Taxonomies**, select the taxonomy that you want to modify. 3. On the **Nodes** tab, you can do the following: | Task | How to do it | |-----------------------------|------------------------------| | Add a child node | Click **Add child node** next to the node. | | Edit name of existing node | Click to update the name of the node. | | Move node to a new parent | Click and select a new parent from the list of existing nodes. | | Delete the node | Click to delete the node. You cannot delete a taxonomy node, if it is used in a content entry. | 4. On the **Settings** tab, you can modify the display name and the API ID of the taxonomy, and add or update the description, if needed. ## Delete a taxonomy 1. Navigate to the **Schema** builder. 2. Select the taxonomy you want to delete. 3. Click **...** (context menu), and then **Delete** to delete the taxonomy. ## Related docs - [Best practices for taxonomies](/docs/developer-guides/schema/taxonomies-best-practices) - Recommended strategies for designing and maintaining taxonomies. - [API reference for taxonomies](/docs/api-reference/schema/taxonomies) - API reference document on fetching taxonomy and taxonomy nodes. - [Filter taxonomies using the Content API](/docs/api-reference/content-api/filtering#taxonomy) - How to filter taxonomies using the Content API. - [Filter taxonomies in the content table](/docs/editor-guides/content/filter-content#taxonomy) - How to filter entries by taxonomies in the content table. --- # Best practices for taxonomies Source: https://hygraph.com/docs/developer-guides/schema/taxonomies-best-practices Taxonomies are hierarchical, centrally managed vocabularies that classify content across multiple models. Taxonomies include consistent labels that improve findability, search and filter UX, personalization, and governance. ## When to use taxonomies Use taxonomies when you need: - A shared classification system reused across multiple models, such as a Blog, Product, Case study, Event - A hierarchy of terms. We recommend 3 nesting levels. Example: `Root → Category → Subcategory → Topic` - Hygraph supports up to 6 nesting levels, not counting the root. - Consistent editorial language when terms change rarely. - Filters in apps and custom views. Currently, you cannot localize taxonomy fields in Hygraph. For locale‑specific category trees that diverge per language, you have two options: - **Same structure, but different labels:** Keep one taxonomy, and map term IDs to localized labels in your app or a helper model. - **Different structures per locale:** Create separate taxonomies, such as `Category_EN`, `Category_DE`, or use a localized Category model with references. ## When not to use taxonomies We recommend that you do not use taxonomies in the following scenarios: | Need | Use | |-------|-----| | Entity with fields or metadata on the nodes. Model lifecycle, validation, and localization may apply. For example, you want additional information such as logos, descriptions, links, country codes, SEO, and so on. | Model and reference | | The values are per‑entry, ad‑hoc, or dynamic. For status, type, or flags, a small, closed, dynamic set of values is useful. | Enumeration | | Navigation or menus that need ordering, audience rules, or content per node, compose into models. Optionally, mirror high‑level menu groups in a taxonomy only if you need to use filters. | Model and component | | Complex relationships or additional business logic. For example, creating a Compatibility taxonomy with nodes for each product and tagging Product A with Product B implies "A is compatible with B". This duplicates your products inside the taxonomy and does not let you capture details like "since version 2.0", "only in Europe", or "one-way vs. two-way compatibility". | References (possibly with a join model) | ## Decision flowchart ![Taxonomy decision flowchart](/images/docs/developer-guides/taxonomy-decision-flowchart.png) Create a taxonomy only if your answer is `YES` to the following questions: 1. Will the labels be **reusable**? Is this a vocabulary that should be centrally managed? Taxonomies are especially useful if reused across models, but even within a single model, they’re the right choice when you need a hierarchy rather than an enumeration. 2. Do the labels need to be **hierarchical**? Do we benefit from parent-child grouping? 3. Will the labels be **stable**? If you answered No to any question, consider the following modeling options: - No to Question 1 → **Enumeration** or **model-specific field** - No to Question 2 → **Enumeration** or a **flat reference model** - No to Question 3 → **Enumeration** or **string** ## Taxonomy patterns A high-level overview of the taxonomy patterns: - Up to three nesting levels (excluding the root) – Recommended for most use cases. - Four to five nesting levels - Use only when following external standards or justified by customer requirements. If your taxonomy goes deeper without clear justification, you are possibly introducing other dimensions, such as audience, format, or region. Rather than extending a single hierarchy, you can model these as enumerations, additional taxonomies, or references. ### Flat taxonomy A flat taxonomy, `Root → Child`, means everything sits one level below the root. This is possible, but it works like a long enumeration. Use this option only when you know you will expand this into a hierarchy later. Avoid placing a large number of siblings directly under the root, as this significantly reduces editor usability. ### Two-level taxonomy A two-level taxonomy, `Root → Parent → Child`, is the most common structure. Example: `Root → Industry → Sub-industry` or `Root → Category → Subcategory`. This approach provides editors with a clear structure while avoiding unnecessary complexity, and it enables effective filtering for end users. ### Three-level taxonomy A three-level taxonomy, `Root → Level 1 → Level 2 → Level 3`, works well for complex content sets. It provides enough depth and remains manageable. ### Four-level and deeper taxonomy A four+ level taxonomy (Root → up to Level 6) is valid in industries with established classification standards, such as the Global Industry Classification Standard. Hygraph supports up to 6 nesting levels, without counting the root. Such taxonomies are very powerful, but there could be complexity due to governance. Merges, splits, and reclassifications become heavy operations. ## Implementation tips in Hygraph Begin with a simple taxonomy, then iterate based on usage data. Avoid over-engineering. - Assign an owner per taxonomy. Restrict who can create or rename nodes in production, and ensure that significant changes to the taxonomy go through a proper review. By default, editors can create taxonomy nodes. You can use custom roles if you want to limit who can create taxonomy nodes. - Set up a naming convention for the taxonomy nodes: - Display name - This is the node name that editors see. Keep it concise, and preferably use sentence case. - API ID - This is used to query the taxonomy and its nodes. Ensure that it is stable, machine-safe. Avoid renaming this later. - Keep taxonomy depth less than or equal to three nodes. - Add, deprecate, and remap nodes versus deleting them. Deleting nodes that are in use is disruptive. - [Allow multiple taxonomy values](/docs/api-reference/schema/field-configuration#allow-multiple-values) in models only if required. - Build out taxonomies in a testing environment. Use the testing environment to manage breaking changes. Use change logs or [schema as code](/docs/api-reference/schema/schema-as-code) to maintain previous versions. - Keep trees separate for different purposes in larger projects. --- # Using enumerations Source: https://hygraph.com/docs/developer-guides/schema/using-enumerations ## Overview An enumeration is a predefined set of values that represents a list of possible options. [Check out our API Reference document on enumerations](/docs/api-reference/schema/enumerations) ## What you can do - You can use enumerations to group values within a type. - You can use enumerations to create a list of values and add it to a model. The values can then be selected from a dropdown menu in the content creation screen. ## Create an enumeration 1. Navigate to the **Schema** builder 2. You can create an enumeration by navigating to the Schema builder, locating enumerations on the Schema list, and then clicking on `+ Add` next to Enumerations on the Schema list, or the `+ Add Enumerations` button located at the center of the screen. The **New enumeration** screen will display as a result. 3. Complete the `Display name` field with the name that will be displayed in Hygraph. The `API ID` field will be autocompleted when you complete the `Display name`, but you can edit this value. The API ID is the ID for accessing this enumeration through the API. 4. Optionally, you can add a `Description`. 5. Under enumeration values, you can add a list of values by manually entering the `Display name`, which autocompletes the `API ID` field, and then clicking `+ Add`. Your values will display on a list below. 6. Once you have added all the enumeration values, click `Add Enumeration` at the bottom of the screen to finish the process. ## Use demo enumeration ![Create an enumeration](/images/docs/user-guides/enumerations/enumerations-use-demo.png) 1. Navigate to the **Schema** builder. 2. Click on `Add demo enumeration`, located at the center of the screen. 3. The system will generate the demo enumeration and show you the enumeration details screen. ## Delete an enumeration 1. Navigate to the **Schema** builder. 2. Click on the enumeration you want to delete. 3. Click on the context menu to display the `Delete` option, then click on it to delete the enumeration. ## Add an enumeration to a model 1. Navigate to the **Schema** builder. 2. Click on the model you wish to add the enumeration to. 3. Select the **Enumeration** field from the **Add fields** right sidebar. 4. Complete the `Display name` field. This will autocomplete the `API ID` field, but you can edit this value if you want. 5. Use the `Enumeration` dropdown to select the enumeration you want to add to this model. 6. Optionally, you can also add a `Description`. 7. This screen allows you to control different properties of your **Enumeration** field: you can allow multiple values, localize the field, or use it as title field, by selecting the checkboxes under the `Field options` section of the screen. 8. Click `Add` to save. After an enumeration has been configured for your model, you will be able to use it in the content editor. --- # Variants Source: https://hygraph.com/docs/developer-guides/schema/variants Today, users expect personalized content tailored to their preferences. Generic content is less effective because users prefer relevant content that appeals to them. With the introduction of Variants, Hygraph empowers content teams to deliver tailored content experiences that resonate with specific user Segments. This level of personalization not only enhances user engagement but also drives conversion rates. ## Key terms **Segments** - A named reference used to target a group of users based on shared characteristics or behaviors. The actual user lists for these groups are defined and managed in an external system. You use Segments to deliver specific content, that is, Variants to these external user groups. Examples of shared characteristics for segmentation are: - Demographics: Age, gender, location, language - Behavioral data: Pages visited, products viewed, time on site - Engagement: Frequency of visits, previous purchases, email interactions **Variants** - Personalized versions of content entries that are served depending on the defined Segment. Variants define what version of the content a Segment sees. A main content entry can have many Variants, each tailored to a specific Segment. These Variants are not separate entries but linked to the main entry. They inherit structure or layout from the main entry while allowing customization of certain fields. ## Benefits With Variants, you can create different pieces of content for different audiences. Here are a few benefits of Variants: - Enables personalization and A/B testing use cases - Provides support for targeted content without duplicating entries - Helps manage multi-regional or multi-brand strategies efficiently ## Use cases Hygraph doesn’t decide which content Variant is shown to your end-users, and we don’t collect or process any end-user data to drive Segment creation. While it is possible to build personalized experiences without an additional tool, we recommend using a personalization engine for more complex setups. Here are a few use cases that Variants support: - Show different homepage banners to users in Germany vs. the US. - Highlight specific content to users based on their browsing history, or the platform they come from. - Display metric units for Europe and imperial units for the US on a product page. - Present different introductions or visuals depending on whether the reader is a developer or a marketer. ## Get started **Prerequisites** - You have already set up a content model. - You have created at least one content entry based on the content model. **Permissions** Variants inherit permissions from their content model. So if a user can read, create, and update a model, they have the same permissions for Variants of entries based on the model. **Limits** - Variants are an enterprise feature. Contact our sales team for access. - You can create a maximum of 30 Variants per entry. - Segments and Variants count toward the Content entries limits of your [billing plan](/docs/developer-guides/project/update-billing#commercial-limits). **Steps** 1. [Enable Variant support for fields](#enable-variant-support-for-fields). 2. [(Optional) Modify your Segment system model](#optional-modify-your-segment-system-model). 2. [Add Segments](#add-a-segment). 3. [Add Variants for the main entry and link the Variant to one or more Segments](#add-a-variant-and-link-it-to-a-segment). ## Enable Variant support for fields First, you need to enable Variant support on a per-field basis in your schema. 1. Go to the Schema Editor, and choose your content model. 2. On the **Fields** tab, click **Edit field** for the field that you want to add Variants. - Currently, Variants are not supported for custom fields. Our team is working on adding this functionality to the feature, and we’ll share new additions in future updates. 3. Under **Settings**, select the **Enable variants** check box. - You cannot enable Variant support for unique fields. 4. Click **Update**. Repeat these steps for as many fields in the schema, as required. If you disable Variant support for a field or delete a field that has Variant support enabled, the field and its associated content is removed from all Variants. ## (Optional) Modify your Segment system model The Segment system model includes certain fields, by default. You can add more fields to the Segment model from the Field Types sidebar. Currently, Variants are not supported for custom fields. You cannot localize the fields of the Segment model. ## Add a Segment Next, add Segments to display content from different Variants. 1. In the Content Editor, under **Segments**, go to **All**. 2. Select **Add entry**. 3. Provide the name of the Segment and optionally, add a description. 4. Click **Save** or **Save & publish**. Repeat the above steps to add multiple Segments. ## Add a Variant and link it to a Segment 1. Go to the Content Editor, and open your content entry. This is the main content entry. 2. In the right sidebar, under **Variants**, select **Add**. A new version of the content entry is displayed. The values from the main entry for the default locale are copied into the Variant entry. This makes it easier to make adjustments, especially when the Variant only differs slightly from the main entry. - To add a new locale to the Variant, in the right sidebar, under **Localizations**, click the `+` icon next to the locale. The empty localized fields appear and you can enter the localized text for the Variant in these fields. 3. Only the fields that have Variant support enabled are available for editing. Make changes to the available fields, as required. - Turn on the **Show all fields** toggle to view the rest of the fields in the content model in read-only mode. If you want to edit the read-only fields, you need to go back to the content entry. 4. Now, click **Select segment**. 5. Choose one or more Segments that this Variant applies, and click **Add selected entries**. 6. Click **Save** or **Save & publish**. You need to publish the Segment to the same stage as the Variants to see Variants of a certain Segment. ## Modify a Variant After you save a Variant, you can modify the values in different fields of the Variant. You can also modify the Segments that you linked to this Variant. - Link more Segments - Select the Segment that you added, and click **Add segment**. - Unlink a Segment - Hover over the context menu next to a Segment, and click **Remove**. ## Behavior of Variant fields Each variant maintains its own values independently. If the same content is needed across entries, you need to manually update it in each entry. When working with variant fields, note the following points: - Main entry updates: Updating a variant-enabled field in the main entry does not update the same field in its variants. - Variant Entry updates: Changes made to variant fields do not affect the corresponding fields in the main entry. Publishing the main entry doesn’t auto-publish its variants. You need to publish each variant separately. To be able to publish a variant, you must have already published the main entry. ## Next steps [Use Hygraph's Variants to achieve personalization in your setup](/docs/recipes/personalization/personalization-variants), whether you are just starting out or already have a personalization engine or a Customer Data Platform (CDP) in your setup. You need to identify user Segments in real time, and then dynamically deliver content based on the Variants that you set up in Hygraph. ## Related docs - [API reference for Variants](/docs/api-reference/schema/variants) - API reference document on fetching Variants and Segments. - [Set up live preview for Variants](/docs/developer-guides/schema/live-preview#add-a-variants-preview-url) - Learn how to set up live preview for you Variants after you've set up a preview for your main content entry. - [Achieve Personalization with Hygraph's Variants](/docs/recipes/personalization/personalization-variants) - Learn how to combine variants and segments to create personalized content experiences. - [Set up personalization with Hygraph's Variants and frontend logic](/docs/recipes/personalization/personalization-example-variants-frontend-logic) - Practical guide to implement variant-based personalization in your frontend application. - [Create Variants of content entries](/docs/editor-guides/content/create-variants) - As an editor, learn how to create and edit variants from the main content entry. --- # Visual editing Source: https://hygraph.com/docs/developer-guides/schema/visual-editing-overview Visual editing lets editors review and change content directly from the frontend, without navigating the schema to find the right field. As a developer, you configure it once, and then editors can work from the preview. Hygraph provides two tools for this: - Live preview - Click to Edit Both tools use the same Preview widget. You configure it once for each modelin the Schema builder. After that, live preview works immediately. Click to Edit requires additional SDK setup. | | Live preview | Click to Edit | |--------------------------------------|--------------|-------------------| | Requires Preview widget | Yes | Yes | | Requires Preview SDK | No | Yes | | Requires data attributes on frontend | No | Yes | | Editors see content in context | Yes | Yes | | Editors can jump to a specific field | No | Yes | | Locale switcher in preview panel | Optional | Optional | | Works outside Studio (standalone) | No | Yes | | Real-time updates while typing | No | (Optional) Yes | ## Live preview Live preview loads your frontend in a side-by-side iframe inside Studio. Editors open an entry, click **Open live preview**, and see exactly how their draft content looks alongside the entry form. When they save, the preview refreshes. ![Live preview](/images/docs/developer-guides/visual-editing-live-preview.png) For live preview to work, you need to add a Preview widget to the model in the Hygraph schema and configure a preview URL that serves your content from the `DRAFT` stage. If your project uses multiple locales, adding `{locale}` to the preview URL template activates a locale switcher in the preview panel. Editors can switch between locales directly in the preview without leaving Studio. Start here if you want editors to see content in context before going further. Live preview has no SDK dependency and no frontend instrumentation. It is the fastest path to a working preview. For more information, see [Live preview setup](/docs/developer-guides/schema/live-preview). ## Click to Edit With Click to Edit, editors hover over any tagged element in the preview and click **Edit** to jump directly to that field in Studio. No manual navigation is required. ![Click to Edit](/images/docs/developer-guides/visual-editing-click-to-edit.png) For Click to Edit to work, you need to install the Hygraph Preview SDK in your frontend and add `data-hygraph-*` attributes to the elements you want to make editable. You instrument the fields that matter, and not everything on the page. Start here if live preview is already working and editors are still spending time navigating to fields in the entry form. Click to Edit is an additive step, not a replacement for live preview setup. See [Click to Edit setup](/docs/developer-guides/schema/click-to-edit) and the [Next.js App Router implementation](/docs/developer-guides/schema/click-to-edit-next-js-app-router) and [Next.js Pages Router implementation](/docs/developer-guides/schema/click-to-edit-next-js-pages-router). ## Where to begin If you have not set up either tool yet, follow this order: 1. [Live preview](/docs/developer-guides/schema/live-preview): Add the Preview widget to your model, define your preview URL template, and configure your frontend to serve `DRAFT` content. 2. [Click to Edit](/docs/developer-guides/schema/click-to-edit): Install the Preview SDK and add data attributes to your frontend elements. - [Next.js App Router](/docs/developer-guides/schema/click-to-edit-next-js-app-router): Framework-specific implementation for Next.js App Router with code examples for simple fields and components. - [Next.js Pages Router](/docs/developer-guides/schema/click-to-edit-next-js-pages-router): Framework-specific implementation for Next.js Pages Router with code examples for simple fields and components. --- # Deploy your Gatsby Cloud project with Hygraph webhooks Source: https://hygraph.com/docs/developer-guides/webhooks/deploy-gatsby-project The Gatsby integration has been deprecated and no longer receives support. Please migrate to using webhooks by following the steps shown Here . If you are using **Gatsby Cloud** for your deployments, you can use **Hygraph webhooks** to automatically deploy when content changes. ## Prerequisites - You must have a **Hygraph** account. If you don't have one, you can create one here. - You must have a **Hygraph** project. Read [this](/docs/getting-started/fundamentals/quickstart#create-a-project) document to access further information on how to create Hygraph projects. - You must have an **Gatsby Cloud** account. If you don't have one, you can create one here. - You must have a **Gatsby Cloud** site. Read this document to access further information on how to get started with Gatsby Cloud. **Webhooks are environment specific**. This means their configuration is applied per environment. Take this into consideration if you're working with a project using more than one environment. ## Your Gatsby Cloud site You can use your pre-existing Hygraph powered Gatsby site in Gatsby Cloud - if you have one - or you can create a new site by importing your pre-existing Gatsby project from a GitHub repository. You can follow this guide for instructions on how to do this. During the setup process, you will be asked to provide the necessary environment variables for your Gatsby site to build: ![Environment variables in Gatsby Cloud](/images/docs/user-guides/webhooks/gatsby/gatsby-environment-variables.png) You will find this information in your Hygraph project by navigating to **Project settings > API access**. | In Hygraph | In Gatsby Cloud | | --------------------- | ---------------- | | Content API | HYGRAPH_ENDPOINT | | Permanent Auth Tokens | HYGRAPH_TOKEN | You will find the **Content API** URL under **Endpoints**. Scroll down to the bottom of the screen to find the **Permanent Auth Tokens**. You can follow the same process for Build variables and Preview variables in Gatsby Cloud. ## Configure webhooks The first thing you need to do is navigate to your Gatsby Site, go to **Settings > Webhook**, and copy your **Builds Webhook**. Follow these steps afterwards: 1. In Your Hygraph Project, navigate to **Project Settings > Automation > Webhooks** in your Hygraph project, and click `Add webhook`. 2. The `Add webhook` screen will display. Complete the following information: - **Name:** Name for your webhook. - **Description:** Description for your webhook. Optional field. - **Include payload:** Switch. True by default. If true, data will be sent via the webhook. - **Method:** Dropdown menu that allows you to select an HTTP method from a list. The default value is **POST**, but you can also select **DELETE**, **GET** or **PUT**. - **URL:** Webhook URL. This is where you paste the **Builds Webhook** that you copied from your Gatsby Site settings. - **Secret key:** You can optionally add a secret key to sign the payload of your webhook. The secret key allows the other end to check that the source is genuine. This can be used to verify requests and, for instance, avoid triggering deploys from unknown sources. - **Triggers:** This is where you select which Models, Stages, Actions and Sources should trigger your webhook. Each item has a dropdown menu, where you can select one or more options. Selecting none acts as a wildcard, then all models will trigger the webhook. If you're a developer, [this document](/docs/api-reference/basics/webhooks#triggers) contains some more information on triggers that might be useful to you. - **Headers:** Here you can configure your headers by adding additional **Key** and **Value** data, then clicking on the `Add` button. You can add multiple **key-value** sets, and you can delete the ones you added by clicking on the trash icon. Some endpoints require authentication using API Keys, which could be set here. ![Gatsby webhook example - Headers](/images/docs/user-guides/webhooks/gatsby/gatsby-keyvalue.png) Follow the same process to create a second webhook using the Preview webhook from your Gatsby cloud site if you want to create a preview link as well. ## Trigger a Gatsby Cloud build from Hygraph Once the webhook has been [configured](/docs/developer-guides/webhooks/deploy-gatsby-project#configure-webhooks), performing one of the trigger actions will trigger a build in Gatsby Cloud. For instance, if the trigger actions you configured for the webhook are `PUBLISH` and `UNPUBLISH`, doing either of those actions in the **Content editor** will trigger a new build in Gatsby Cloud. You will be able to check the new build in your **Hygraph project** by checking the [logs](/docs/api-reference/basics/webhooks#webhook-logs). You can also see this in your in your **Gatsby Site**, under **Master > Build history**. ## Migrate from Gatsby integration to webhooks Follow these steps to migrate from your Gatsby integration to webhooks: 1. In your **Hygraph** project, navigate to **Apps** and locate your Gatsby integration card. 2. Click on the trash icon to delete. 3. Finally, navigate to Webhooks and follow the [configuration process](/docs/developer-guides/webhooks/deploy-gatsby-project#configure-webhooks). ## Resources - Gatsby Cloud documentation --- # Deploy your Vercel project with Hygraph webhooks Source: https://hygraph.com/docs/developer-guides/webhooks/trigger-static-build If you are using Vercel for your deployments, you should check our official integration for a one-click build button. Alternatively, if you want a hands off approach, you can use Hygraph webhooks to automatically deploy when content changes. **Webhooks are environment specific**. This means their configuration is applied per environment. Take this into consideration if you're working with a project using more than one environment. ## 1. Create your deploy hook Inside your Vercel project Git settings, scroll to the "Deploy Hooks" section. You'll want to specify the name of your hook, and the Git branch name you want to trigger a deployment for. The name is only a reference to you, and you should never share these deployment URLs, as they're public, and don't require any authentication to call. ![Vercel Deploy Hooks form](https://eu-central-1-shared-euc1-02.graphassets.com/AvHQ3RDvFSousA8iwElOKz/C9OgYS2RR5myXEplaiwP) Once you've configured your hook, create it, and copy the URL. ![Deploy published content on master](https://eu-central-1-shared-euc1-02.graphassets.com/AvHQ3RDvFSousA8iwElOKz/6XdFUbySlyhtODVugKoV) ## 2. Configure Hygraph Now navigate to **Project Settings > Automation > Webhooks** in your Hygraph project, and click `Add webhook`. Give your webhook a name, and paste your Vercel deploy hook URL inside the `URL` input. You can disable including the payload. ![Hygraph webhook config](https://eu-central-1-shared-euc1-02.graphassets.com/AvHQ3RDvFSousA8iwElOKz/XCUte3ONRAK6uAOFX9LN) Next you will want to configure the triggers which call the Vercel deploy hook. You can specify: **Content Model** You can specify one or more content models within your project that causes this webhook to trigger. If you don't specify any, then any content model will be linked to this webhook. **Stage** In some cases you may want to deploy a custom content stage such as DRAFT to a staging environment. When you need to do this, you'll most likely want to duplicate your project inside Vercel, connected to the same repo, and repeat step 1. In this example, we'll select `Published` as the stage we're watching for content to be published to. This means if someone is drafting a post inside Hygraph, then our Vercel deploy hook will not be called. **Action** You can specify one or more actions to watch to trigger your Vercel build hook. In this example, we want to redeploy our Vercel project whenever content is published, or unpublished, so we'll opt to leave this field empty. ![Hygraph webhook triggers](https://eu-central-1-shared-euc1-02.graphassets.com/AvHQ3RDvFSousA8iwElOKz/1Pe63XUTMK313DHd4XL8) ## 3. Publish content and deploy Now head to your content editor, make a change, and click "Save and publish". You'll then notice inside your Vercel deployments list there is a new build. ![Save and publish button](https://eu-central-1-shared-euc1-02.graphassets.com/AvHQ3RDvFSousA8iwElOKz/8DWEiZH4TUGyHMQ8mcXO) That's it! --- # Webhooks overview Source: https://hygraph.com/docs/developer-guides/webhooks/webhooks-overview ## Overview This document shares general information about the use of webhooks with Hygraph, such as configuring webhooks, viewing logs, and editing or deleting webhooks. For specific information about the use of webhooks with external apps, please access the corresponding guide using the left side navigation menu. [Check out our API Reference document on webhooks](/docs/api-reference/basics/webhooks) ## Configure webhooks To configure webhooks, follow these steps: ![Add a webhook](/images/docs/user-guides/webhooks/webhooks-create.png) 1. In your project, navigate to **Project Settings > Automation > Webhooks**. 2. Click `Add webhook`. 3. The `Add webhook` screen will display. Complete the following information: - **Name:** Name for your webhook. - **Description:** Description for your webhook. Optional field. - **Include payload:** Select this checkbox to allow applications to send and receive data packets. - **Method:** Dropdown menu that allows you to select an HTTP method from a list. You can select **POST**, **DELETE**, **GET** or **PUT**. - **URL:** Webhook URL. This is information that you will get from the connected app. Please check the corresponding guide for specific information about this. - **Secret key:** You can optionally add a secret key to sign the payload of your webhook. The secret key allows the other end to check that the source is genuine. You can use this to verify requests and, for instance, avoid triggering deploys from unknown sources. - **Triggers:** This is where you select which **Models**, **Stages**, **Actions** and **Sources** should trigger your webhook. Each item has a dropdown menu, where you can select one or more options. Selecting none acts as a wildcard, then all models will trigger the webhook. [This document](/docs/api-reference/basics/webhooks#triggers) contains some more information on triggers. - **Headers:** Here you can configure your headers by adding additional **Key** and **Value** data, then clicking `+ Add`. You can add multiple **key-value** sets, and you can delete the ones you added by selecting the `Remove` option in the context menu. Some endpoints require authentication using API Keys, which could be set here. ![Webhook headers](/images/docs/user-guides/webhooks/webhooks-keyvalue.png) ## View webhook logs 1. In your project, navigate to **Project Settings > Automation > Webhooks**. 2. Click **View logs** next to the webhook for which you want to check logs. The logs screen will display as a result. This screen allows you to monitor the recent activity of your webhook from the last 7 days. **Clicking on a log item displays its details:** ![Webhook logs](/images/docs/user-guides/webhooks/log-details.png) ## Disable or enable a webhook ![Webhook status](/images/docs/user-guides/webhooks/webhooks-status.png) You can see whether a webhook is set to `Active` or `Inactive` next to its name on the webhook card. ![Disable a webhook](/images/docs/user-guides/webhooks/webhooks-disable.png) To disable or enable a webhook, use the context menu and select `Pause` / `Resume`. ## Edit or delete a webhook ![Webhook - Edit and delete icons](/images/docs/user-guides/webhooks/edit-delete-webhooks.png) Click on the context menu and select `Delete` to delete the webhook, or on `Edit` to edit its details. You can learn more about each of the fields you can edit [here](/docs/developer-guides/webhooks/webhooks-overview#configure-webhooks). --- # Migration to Hygraph Asset Management System Source: https://hygraph.com/docs/api-reference/assets/asset-migration ## Overview - The new asset system migration tooling is only be available for Hygraph Studio. - We will automatically migrate free projects that have not migrated by the **end of April, 2025**. - Paid & Enterprise projects have an extended migration timeline until **end of May, 2025**. At the moment, Hygraph projects can use one of two asset systems. Projects created after February 2024 will use the new **Hygraph Asset Management** system, while older projects will use the **Legacy asset system**. This document discusses the differences between both systems and the migration flows available. If your project uses the old asset system and needs to migrate to the new one, the UI will show you that an action is required: ![Asset migration - action required](/images/docs/api-reference/assets/asset-migration-required.png) When you go to **Project Settings > Environments**, you will see this. ![Asset migration - action required](/images/docs/api-reference/assets/migration-required.png) - **Paid projects** can create a [test environment for asset migration](/docs/api-reference/assets/asset-migration#migration-environment), but **free projects** can't. - **All projects** can migrate the master environment and others ["in-place"](/docs/api-reference/assets/asset-migration#in-place-migration), turning them into read-only during migration. They can do this one environment at a time. ## Changes The systems differ in four important areas. This section shows you a comparative view of these differences. ### Asset URL The URL the new Hygraph Asset Management system uses to deliver assets slightly differs from the Legacy system. Instead of the [`media.graphassets.com`](http://media.graphassets.com) URL that the Legacy Asset System uses, the new URL uses a regional subdomain and so it looks like this: [`regionalSubdomain.graphassets.com`](http://regionalSubdomain.graphassets.com). For example, if your project is hosted in our shared Germany (Frankfurt) region, the domain will be [`eu-central-1-shared-euc1-02.graphassets.com`](http://eu-central-1-shared-euc1-02.graphassets.com/). Additionally, the old asset URL only contains a unique handle and some additional transformations following this format: ```json media.graphassets.com//handle ``` The new Hygraph Asset Management system URL includes an additional environment identifier and an optional filename at the end: ```json .graphassets.com///handle/filename.jpg ``` The optional filename at the end of the URL needs to match the filename used to upload the asset. The file extension (`jpg`, `webp`, etc.) needs to match the current version of the asset. If you convert a `jpg` to `webp` using URL transformations, that extension at the end needs to be `webp`. #### environmentId With the Hygraph Asset Management System, the asset URL includes the identifier `environmentId`, like so: ```json .graphassets.com///handle/filename.jpg ``` This `environmentId` is the asset config `apikey` from a specific environment. You can easily get this ID in the API Playground, from the management API: ```graphql { viewer { project(id: "...") { environment(name: "master") { assetConfig { apiKey #<-- this is the "envID" } } } } } ``` Select the `Management API` in the API Playground to get this information. To do this, use the menu at the top-left of the API Playground, like so: ![Management API in the API Playground](/images/docs/api-reference/assets/management-api-in-playground.png) #### Next.js For Next.js you will need a new configuration for the asset domain. Since the images are in the Hygraph CDN, you need to specify our domain in the `next.config.js` file. For more information, check this guide. ```js module.exports = { images: { remotePatterns: [ { protocol: 'https', hostname: '**.graphassets.com', }, ], }, }; ``` #### Astro For Astro you will need a new configuration for the asset domain. Since the images are in the Hygraph CDN, you need to specify our domain in the `astro.config.mjs` file: ```js export default defineConfig({ // ... Rest of the configuration image: { domains: ["https://**.graphassets.com"], }, }); ``` ### Transformations The Legacy Asset system natively supported the URL transformations `resize` and `output`. If you were using other undocumented transformations, please reach out to us or check in the following information if the new system supports them. The new Hygraph Asset Management system supports additional transformations: - [resize](/docs/api-reference/assets/transformations#resize) - [blur](/docs/api-reference/assets/transformations#blur) - [border](/docs/api-reference/assets/transformations#border) - [compress](/docs/api-reference/assets/transformations#compress) - [crop](/docs/api-reference/assets/transformations#crop) - [quality](/docs/api-reference/assets/transformations#quality) - [sharpen](/docs/api-reference/assets/transformations#sharpen) - [auto_image](/docs/api-reference/assets/transformations#auto-image) - [output](/docs/api-reference/assets/transformations#file-type-conversion) (File type conversions) **File Conversions (output)** At the moment, the Hygraph Asset Management system only supports transformations between image mime types. [Check out this table to learn about this in detail](/docs/api-reference/assets/transformations#file-type-conversion). If you were using more advanced transformations, like `xls to pdf` or other unsupported ones, please reach out to us and let us know. ### Uploading Assets via API While the Legacy Asset System used the Asset Upload API, with the new Hygraph Asset Management system asset uploads are a part of the native GraphQL API. You can still upload assets using a [remote URL](/docs/api-reference/assets/uploading-assets#upload-by-remote-url) or a [local file](/docs/api-reference/assets/uploading-assets#upload-by-file), though the process is slightly different. Size limits for uploaded files depend on the plan. Check out our pricing page. Make sure you read our [webhooks documentation](/docs/api-reference/basics/webhooks#webhooks-and-assets) to understand how they work with the Hygraph Asset Management System. #### Upload by file **Uploading a local file** is now a two-step process. You will need to first call a `createAsset` mutation passing some basic information about the asset, and then use the returned information to send the actual file via a pre-signed URL. [Our documentation explains this process in detail](/docs/api-reference/assets/uploading-assets#upload-by-file). #### Upload by remote URL The **Upload assets by remote URL** method lets you upload assets in the GraphQL API by passing the URL of an asset hosted somewhere publicly accessible in the `createAsset` mutation. Here's an example for you: ```graphql mutation test { createAsset( data: { uploadUrl:"https://images.unsplash.com/photo-1682687218147-9806132dc697" } ) { id url } } ``` ### Upload Status The new Hygraph Asset Management System handles the uploading process asynchronously, and so asset entries now have an **upload status**. This is mostly relevant when using **Upload by file**, but also applies to **uploading via remote URL**, except we handle the fetching of the remote asset directly. When first calling the `createAsset` mutation, the asset entry is created in an `ASSET_CREATE_PENDING` status while we wait for the file to be sent to us via the pre-signed URL. After successfully receiving the file, the status switches to `ASSET_UPLOAD_COMPLETE`. If there is an error, it will switch to `ASSET_ERROR_UPLOAD` instead. Make sure you read our [webhooks documentation](/docs/api-reference/basics/webhooks#webhooks-and-assets) to understand how they work with the Hygraph Asset Management System. If you do not send a file within the default time window, the asset entry stays in `ASSET_CREATE_PENDING` and might be cleaned up eventually. Be aware that all non-successful asset uploads are hidden by default on the API, but can be retrieved like this: ```graphql { assets(where: { upload: {status_not_in: ASSET_UPLOAD_COMPLETE} }) { createdAt id publishedAt fileName url updatedAt } } ``` ### Upload Widget The upload widget in the Hygraph UI is different depending on the asset system that your project uses. Projects that use the new Hygraph Asset Management system can see a new upload widget within the webapp. For now, it only offers uploading a local file, but will soon be extended to also support uploading a remote URL. This is what it looks like at the moment: ![UI - Hygraph Asset Management system](/images/docs/api-reference/assets/hygraph-asset-management-system-ui.png) Compared to this, the upload widget for the Legacy Asset system offers options to upload local files, upload using a remote URL, doing a web search, as well as using Facebook, Instagram and Google Drive: ![UI - Legacy Asset system](/images/docs/api-reference/assets/legacy-asset-system-ui.png) If you'd like to see any of these options brought back or if you have additional suggestions, please let us know! ### Webhooks If your project uses webhooks with the Hygraph Asset Management System, be aware that asset uploads are asynchronous. "Create" webhooks are triggered before uploads complete, causing potential issues if external systems expect assets to be available immediately. The asset status starts as `ASSET_CREATE_PENDING` and switches to `ASSET_UPLOAD_COMPLETE` when ready. Learn more about this process [here](/docs/api-reference/basics/webhooks#webhooks-and-assets). ## Migration environment **The new asset system migration tooling is only be available for Hygraph Studio.** This option is only available for paid plans. Please [contact sales](/contact) for further information. The resulting environment cannot be promoted to Master, but you can use it for testing purposes, to assess the impact of the migration and look into what additional actions you may need to take. Every Hygraph project with a paid plan - Growth, Growth with add-ons, and Enterprise - will receive a free environment that allows you to clone your `master` environment using the new asset management system. To start the migration process using a test environment, go to `Project settings > General > Environments` and click `Configure migration`. Select `Clone Master environment with new asset system for testing` on the Asset migration popup. ![Migration environment](/images/docs/api-reference/assets/migration-environment.png) Click `Start migration` to continue. The newly created environment will be a one-to-one copy of your `master` environment with all your assets migrated to use the new system, including all the above mentioned changes. The system assigns the new environment the name **Asset migration**, and a notification at the top of the screen informs you that the environment has been successfully migrated to the new asset system: ![Asset migration environment](/images/docs/api-reference/assets/asset-migration-environment-cloned.png) In that migration environment, you can test how the new asset system behaves and if there are any breaking changes compared to your production environment. [The comparison information](/docs/api-reference/assets/asset-migration#changes) in this document will help you spot potential issues. For instance, since we've explained that asset URLs change with the new system, you might want to look into where you are using those URLs that might need updating. This migration environment can't be promoted to `master` and will be fully removed once the Legacy Asset System is deprecated. Please use it for testing purposes, before using our [in-place migration](/docs/api-reference/assets/asset-migration#in-place-migration) process. After cloning the migration environment, the `Clone Master environment with new asset system for testing` will no longer appear on the **Asset migration** popup ## In-place migration **The new asset system migration tooling is only be available for Hygraph Studio.** The migration flow offers two options for in-place migration. You can migrate your Master environment or others you may have. To start the migration process, go to `Project settings > General > Environments` and click `Configure migration`. On the **Asset migration** popup, select which environment you want to migrate to the new asset system: ![In-place migration options](/images/docs/api-reference/assets/asset-migration-options.png) - `Migrate Master environment`: This option migrates your Master environment to the new asset system. - `Migrate a specific environment other than master`: This option allows you to select other environments you may have. Please note that if you have more than one environment, you will eventually need to migrate all of them. As this migration involves the risk of breaking changes, the system will prompt you to confirm that you have read our migration documentation: ![Asset migration warning](/images/docs/api-reference/assets/asset-migration-warning.png) Click `Start migration` to continue. **Your environment will turn read-only for the duration of the migration process.** During the migration process, the migration status displays on the left side of the screen: ![Migration in progress](/images/docs/api-reference/assets/asset-migration-callouts.png) ### Asset URLs in JSON fields During the migration process, we go through every single `Markdown` and `Rich Text` field, and replace the old asset URL with the new one. However, one field that could potentially store asset URLs as well is the `Single Line`, `Multiple Line`, and `JSON` field, but we don't automatically migrate them. If your project uses these fields to store asset URLs, you will need to update them manually. To do so, you will need to go through all available content entries that use these fields, and check if they contain `media.graphassets.com`. If they do, you should manually replace that URL with the new one. This can also be done by using Content API filters. ## Migration & old URLs Like we mentioned before, the new asset system uses slightly different URLs. Make sure to check for asset URLs stored in other places, such as source code or external systems. We will sunset the old asset domain after the **end of June, 2025**. --- # Assets API reference Source: https://hygraph.com/docs/api-reference/assets/assets-overview ## Overview The Asset model is included with every project, can be modified with custom [Field Types](/docs/api-reference/schema/field-types), and are [localized](/docs/api-reference/content-api/localization) by default, but cannot be deleted. While most commonly used for photos, can support any file type including audio files, zip files, and more. The Asset type extends the [system fields](/docs/api-reference/schema/system-fields). Here are some links that might help you: | Document name | Description | | ------------------------------------------------------------------- | --------------------------------------------------------------------------------- | | [Fetching assets](/docs/api-reference/assets/fetching-assets) | Learn about the 4 query types that Hygraph automatically creates for every asset. | | [Referencing assets](/docs/api-reference/assets/referencing-assets) | Learn how to query assets in relation to a model in your schema. | | [Transformations](/docs/api-reference/assets/transformations) | Learn about asset transformations. | | [Uploading assets](/docs/api-reference/assets/uploading-assets) | Learn how to upload assets by file and by remote URL. | | [Updating assets](/docs/api-reference/assets/updating-assets) | Learn how to use the `updateAsset` mutation. | | [Deleting assets](/docs/api-reference/assets/deleting-assets) | Learn how to delete assets using the `deleteAsset` mutation. | | [Publishing assets](/docs/api-reference/assets/publishing-assets) | Learn how to use the `publishAsset` and `unpublishAsset` mutations. | | [Localized assets](/docs/api-reference/assets/localized-assets) | Learn about asset localization. | | [Embedded types](/docs/api-reference/assets/embedded-types) | Learn how to ember assets into Rich Text fields. | ## Which asset system does my project use? At the moment, Hygraph projects can use one of two asset systems. Projects created after February 2024 will use the new **Hygraph Asset Management** system, while projects that are older than that will use the **Legacy asset system**. If your project is a clone of an older project, it will use the legacy asset system. To learn what system your project uses, check the asset upload popup in the UI. ![UI asset upload](/images/docs/api-reference/assets/ui-asset-upload.png) If your project shows this, you are using the legacy asset system: ![UI - Legacy Asset system](/images/docs/api-reference/assets/legacy-asset-system-ui.png) If your project shows this, you are using the Hygraph Asset Management system: ![UI - Hygraph Asset Management system](/images/docs/api-reference/assets/hygraph-asset-management-system-ui.png) Another way to quickly see which asset system your project uses is to navigate to **Project Settings > Endpoints**, and look for the **Assets** section. If you see this, your project uses the new Hygraph Asset Management system: ![Hygraph Asset Management system](/images/docs/api-reference/assets/hygraph-asset-management-system.png) If you see this, your project uses the Legacy Asset system: ![Legacy Asset system](/images/docs/api-reference/assets/legacy-asset-system.png) While both systems are available, our documentation will provide information on how to work with each. You will find this in our API Reference for Assets in the [transformations](/docs/api-reference/assets/transformations), [asset upload](/docs/api-reference/assets/uploading-assets), and [localization](/docs/api-reference/assets/localized-assets) documents. Simply identify the system your project uses and proceed with that documentation. --- # Border transformation HEX values Source: https://hygraph.com/docs/api-reference/assets/border-hex-values This is a list of HEX values that you can use with the [`border` transformation](/docs/api-reference/assets/transformations#border) in Hygraph: | Color name | HEX value | | -------------------- | ---------- | | aliceblue | `F0F8FFFF` | | antiquewhite | `FAEBD7FF` | | aqua | `00FFFFFF` | | aquamarine | `7FFFD4FF` | | azure | `F0FFFFFF` | | beige | `F5F5DCFF` | | bisque | `FFE4C4FF` | | black | `000000FF` | | blanchedalmond | `FFEBCDFF` | | blue | `0000FFFF` | | blueviolet | `8A2BE2FF` | | brown | `A52A2AFF` | | burlywood | `DEB887FF` | | cadetblue | `5F9EA0FF` | | chartreuse | `7FFF00FF` | | chocolate | `D2691EFF` | | coral | `FF7F50FF` | | cornflowerblue | `6495EDFF` | | cornsilk | `FFF8DCFF` | | crimson | `DC143CFF` | | cyan | `00FFFFFF` | | darkblue | `00008BFF` | | darkcyan | `008B8BFF` | | darkgoldenrod | `B8860BFF` | | darkgray | `A9A9A9FF` | | darkgreen | `006400FF` | | darkgrey | `A9A9A9FF` | | darkkhaki | `BDB76BFF` | | darkmagenta | `8B008BFF` | | darkolivegreen | `556B2FFF` | | darkorange | `FF8C00FF` | | darkorchid | `9932CCFF` | | darkred | `8B0000FF` | | darksalmon | `E9967AFF` | | darkseagreen | `8FBC8FFF` | | darkslateblue | `483D8BFF` | | darkslategray | `2F4F4FFF` | | darkslategrey | `2F4F4FFF` | | darkturquoise | `00CED1FF` | | darkviolet | `9400D3FF` | | deeppink | `FF1493FF` | | deepskyblue | `00BFFFFF` | | dimgray | `696969FF` | | dimgrey | `696969FF` | | dodgerblue | `1E90FFFF` | | firebrick | `B22222FF` | | floralwhite | `FFFAF0FF` | | forestgreen | `228B22FF` | | fractal | `808080FF` | | fuchsia | `FF00FFFF` | | gainsboro | `DCDCDCFF` | | ghostwhite | `F8F8FFFF` | | gold | `FFD700FF` | | goldenrod | `DAA520FF` | | gray0 | `000000FF` | | gray1 | `030303FF` | | gray2 | `050505FF` | | gray3 | `080808FF` | | gray4 | `0A0A0AFF` | | gray5 | `0D0D0DFF` | | gray6 | `0F0F0FFF` | | gray7 | `121212FF` | | gray8 | `141414FF` | | gray9 | `171717FF` | | gray10 | `1A1A1AFF` | | gray11 | `1C1C1CFF` | | gray12 | `1F1F1FFF` | | gray13 | `212121FF` | | gray14 | `242424FF` | | gray15 | `262626FF` | | gray16 | `292929FF` | | gray17 | `2B2B2BFF` | | gray18 | `2E2E2EFF` | | gray19 | `303030FF` | | gray20 | `333333FF` | | gray21 | `363636FF` | | gray22 | `383838FF` | | gray23 | `3B3B3BFF` | | gray24 | `3D3D3DFF` | | gray25 | `404040FF` | | gray26 | `424242FF` | | gray27 | `454545FF` | | gray28 | `474747FF` | | gray29 | `4A4A4AFF` | | gray30 | `4D4D4DFF` | | gray31 | `4F4F4FFF` | | gray32 | `525252FF` | | gray33 | `545454FF` | | gray34 | `575757FF` | | gray35 | `595959FF` | | gray36 | `5C5C5CFF` | | gray37 | `5E5E5EFF` | | gray38 | `616161FF` | | gray39 | `636363FF` | | gray40 | `666666FF` | | gray41 | `696969FF` | | gray42 | `6B6B6BFF` | | gray43 | `6E6E6EFF` | | gray44 | `707070FF` | | gray45 | `737373FF` | | gray46 | `757575FF` | | gray47 | `787878FF` | | gray48 | `7A7A7AFF` | | gray49 | `7D7D7DFF` | | gray50 | `7F7F7FFF` | | gray51 | `828282FF` | | gray52 | `858585FF` | | gray53 | `878787FF` | | gray54 | `8A8A8AFF` | | gray55 | `8C8C8CFF` | | gray56 | `8F8F8FFF` | | gray57 | `919191FF` | | gray58 | `949494FF` | | gray59 | `969696FF` | | gray60 | `999999FF` | | gray61 | `9C9C9CFF` | | gray62 | `9E9E9EFF` | | gray63 | `A1A1A1FF` | | gray64 | `A3A3A3FF` | | gray65 | `A6A6A6FF` | | gray66 | `A8A8A8FF` | | gray67 | `ABABABFF` | | gray68 | `ADADADFF` | | gray69 | `B0B0B0FF` | | gray70 | `B3B3B3FF` | | gray71 | `B5B5B5FF` | | gray72 | `B8B8B8FF` | | gray73 | `BABABAFF` | | gray74 | `BDBDBDFF` | | gray75 | `BFBFBFFF` | | gray76 | `C2C2C2FF` | | gray77 | `C4C4C4FF` | | gray78 | `C7C7C7FF` | | gray79 | `C9C9C9FF` | | gray80 | `CCCCCCFF` | | gray81 | `CFCFCFFF` | | gray82 | `D1D1D1FF` | | gray83 | `D4D4D4FF` | | gray84 | `D6D6D6FF` | | gray85 | `D9D9D9FF` | | gray86 | `DBDBDBFF` | | gray87 | `DEDEDEFF` | | gray88 | `E0E0E0FF` | | gray89 | `E3E3E3FF` | | gray90 | `E5E5E5FF` | | gray91 | `E8E8E8FF` | | gray92 | `EBEBEBFF` | | gray93 | `EDEDEDFF` | | gray94 | `F0F0F0FF` | | gray95 | `F2F2F2FF` | | gray96 | `F5F5F5FF` | | gray97 | `F7F7F7FF` | | gray98 | `FAFAFAFF` | | gray99 | `FCFCFCFF` | | gray100 | `FFFFFFFF` | | gray | `7E7E7EFF` | | green | `008000FF` | | greenyellow | `ADFF2FFF` | | grey | `808080FF` | | honeydew | `F0FFF0FF` | | hotpink | `FF69B4FF` | | indianred | `CD5C5CFF` | | indigo | `4B0082FF` | | ivory | `FFFFF0FF` | | khaki | `F0E68CFF` | | lavender | `E6E6FAFF` | | lavenderblush | `FFF0F5FF` | | lawngreen | `7CFC00FF` | | lemonchiffon | `FFFACDFF` | | lightblue | `ADD8E6FF` | | lightcoral | `F08080FF` | | lightcyan | `E0FFFFFF` | | lightgoldenrodyellow | `FAFAD2FF` | | lightgray | `D3D3D3FF` | | lightgreen | `90EE90FF` | | lightgrey | `D3D3D3FF` | | lightpink | `FFB6C1FF` | | lightsalmon | `FFA07AFF` | | lightseagreen | `20B2AAFF` | | lightskyblue | `87CEFAFF` | | lightslategray | `778899FF` | | lightslategrey | `778899FF` | | lightsteelblue | `B0C4DEFF` | | lightyellow | `FFFFE0FF` | | lime | `00FF00FF` | | limegreen | `32CD32FF` | | linen | `FAF0E6FF` | | magenta | `FF00FFFF` | | maroon | `800000FF` | | mediumaquamarine | `66CDAAFF` | | mediumblue | `0000CDFF` | | mediumorchid | `BA55D3FF` | | mediumpurple | `9370DBFF` | | mediumseagreen | `3CB371FF` | | mediumslateblue | `7B68EEFF` | | mediumspringgreen | `00FA9AFF` | | mediumturquoise | `48D1CCFF` | | mediumvioletred | `C71585FF` | | midnightblue | `191970FF` | | mintcream | `F5FFFAFF` | | mistyrose | `FFE4E1FF` | | moccasin | `FFE4B5FF` | | navajowhite | `FFDEADFF` | | navy | `000080FF` | | none | `000000FF` | | oldlace | `FDF5E6FF` | | olive | `808000FF` | | olivedrab | `6B8E23FF` | | orange | `FFA500FF` | | orangered | `FF4500FF` | | orchid | `DA70D6FF` | | palegoldenrod | `EEE8AAFF` | | palegreen | `98FB98FF` | | paleturquoise | `AFEEEEFF` | | palevioletred | `DB7093FF` | | papayawhip | `FFEFD5FF` | | peachpuff | `FFDAB9FF` | | peru | `CD853FFF` | | pink | `FFC0CBFF` | | plum | `DDA0DDFF` | | powderblue | `B0E0E6FF` | | purple | `800080FF` | | red | `FF0000FF` | | rosybrown | `BC8F8FFF` | | royalblue | `4169E1FF` | | saddlebrown | `8B4513FF` | | salmon | `FA8072FF` | | sandybrown | `F4A460FF` | | seagreen | `2E8B57FF` | | seashell | `FFF5EEFF` | | sienna | `A0522DFF` | | silver | `C0C0C0FF` | | skyblue | `87CEEBFF` | | slateblue | `6A5ACDFF` | | slategray | `708090FF` | | slategrey | `708090FF` | | snow | `FFFAFAFF` | | springgreen | `00FF7FFF` | | steelblue | `4682B4FF` | | tan | `D2B48CFF` | | teal | `008080FF` | | thistle | `D8BFD8FF` | | tomato | `FF6347FF` | | turquoise | `40E0D0FF` | | violet | `EE82EEFF` | | wheat | `F5DEB3FF` | | white | `FFFFFFFF` | | whitesmoke | `F5F5F5FF` | | yellow | `FFFF00FF` | | yellowgreen | `9ACD32FF` | --- # Deleting assets Source: https://hygraph.com/docs/api-reference/assets/deleting-assets Hygraph exposes a `deleteAsset` mutation that you can use to delete any unwanted assets. Simply pass the `id` of the asset you want to delete: ```graphql mutation { deleteAsset(where: { id: "..." }) { id } } ``` Deleting assets is a permanent change, and cannot be rolled back. Learn more about [Mutations](/docs/api-reference/content-api/mutations#delete-entries). --- # Embedded types Source: https://hygraph.com/docs/api-reference/assets/embedded-types Assets can be embedded into the [Rich Text Field Type](/docs/api-reference/schema/field-types#rich-text) via a configuration setting. On the API side, we create a union relation that references the selected model. ## Enable embedding The first thing you need to do is configure your Rich Text field to allow embeds, and select **Asset** as embeddable model. You will do this in the UI by navigating to your Schema, selecting the model your Rich Text field is in, and selecting the `Enable embedding` checkbox: ![Rich Text Options](/images/docs/api-reference/schema/rich-text-embed-options.png) **Rich text embeds need to be enabled per field**. You can do this from inside the **Field Settings** when adding a new, or editing a `Rich Text` field. Click `Enable embedding` and select the models that should be embeddable in your Rich text field. ## Create union relation With Rich Text Embeds enabled, your API will have some new types added. The name of your field will be now a type appended by `RichText`, and `RichTextEmbeddedTypes` inside your schema. For example, if you had the model `Post` and field `content`, the types generates would be `PostContentRichText`, and `PostContentRichTextEmbeddedTypes` respectively. The `PostContentRichText` type will look like the following: ```graphql type RichText { json: RichTextAST! html: String! markdown: String! text: String! references: [PostContentRichTextEmbeddedTypes!]! } ``` The `references` field will be a union relation to the types you embedded, for example `Asset`. You should use the `references` field when querying `JSON` to get the `URL` - with any [transformations](/docs/api-reference/assets/transformations), `handle`, or any of the [Asset fields](/docs/api-reference/schema/system-fields#asset-fields). ```graphql { posts { content { json html markdown text references { __typename ... on Asset { url handle } } } } } ``` ```json { "data": { "posts": [ { "content": { "json": { "children": [ { "type": "paragraph", "children": [ { "text": "Hygraph Rich Text Embeds" } ] }, { "type": "embed", "nodeId": "cko2lq2u0031r0844xnvurz05", "children": [ { "text": "" } ], "nodeType": "Asset" }, { "type": "paragraph", "children": [ { "text": "" } ] } ] }, "html": "

Hygraph Rich Text Embeds

", "markdown": "Hygraph Rich Text Embeds\n\n\n", "text": "Hygraph Rich Text Embeds\\n\\n", "references": [ { "__typename": "Asset", "url": "https://media.graphassets.com/xSIoGkATQybd8S2SgA5Q", "handle": "xSIoGkATQybd8S2SgA5Q" } ] } } ] } } ```
The `HTML` response will return `gcms-embed-type` and `gcms-embed-id` data attributes for the embedded types. A block embed is returned as `div` and an inline embed as `span` with a `data-gcms-embed-inline` attribute. A link embed is returned as an `a`-tag with a `data-gcms-embed-id` and `data-gcms-embed-type` attribute. ```html
```
```html ``` ```html link text ```
Hygraph uses Slate 0.5 for `RichTextAST`. If you are programmatically creating content entries with Rich Text, you should use the @graphcms/html-to-slate-ast package. ## Additional resources - [Rich Text field:](/docs/api-reference/content-api/rich-text-field) Learn more about Hygraph's Rich Text field. The article covers Rich Text data, Rich Text embeds, and using your JSON representation of RTE for customization. - Styling Rich Text with TailwindCSS: Detailed tutorial on how to use the `JSON` representation from the RTE to create custom elements for each text-based element of Rich Text. - Introducing the Hygraph React Rich Text Renderer: Blog post on how to render Hygraph documents using Rich Text in your application easily using our available packages. - [Rich Text editor UI guide:](/docs/developer-guides/content/rich-text-editor) Guide on how to use Hygraph's Rich Text editor in the content editor of your project. --- # Fetching assets Source: https://hygraph.com/docs/api-reference/assets/fetching-assets Hygraph automatically generates 4 query types for assets. These are: ![Asset queries](/images/docs/api-reference/assets/asset-queries.png) - `asset` - `assets` - `assetVersion` - `assetsConnection` These queries work just like regular [queries](/docs/api-reference/content-api/queries), and can be [filtered](/docs/api-reference/content-api/filtering) by their [fields](/docs/api-reference/schema/field-types#asset). Here are some sample queries: ```graphql # Fetch the fileName, local, size and stage of the asset with the provided id query MyQuery { asset(where: {id: ""}) { fileName locale size stage url } } ``` ```graphql # Fetch the fileName, size and locale of all assets in stage PUBLISHED query MyQuery { assets(stage: PUBLISHED) { fileName size locale url } } ``` ```graphql # Fetch an asset version, filtered by revision number, unique identifier, and stage query MyQuery { assetVersion( where: {revision: 10, id: "", stage: DRAFT} ) { createdAt data } } ``` ```graphql # Fetch assets with assetUploadStatus ASSET_CREATE_PENDING query MyQuery { assetsConnection(where: {assetUploadStatus: ASSET_CREATE_PENDING}) { edges { node { id } } } } ``` ## Generate URLs with file names To enhance SEO and improve user trust, you can include the original file name in asset URLs generated via the new Hygraph Asset Management system. 1. Run the following query: ```graphql # Fetch the url and fileName of the asset query MyQuery { assets { url fileName } } ``` ```graphql { "data": { "assets": [ { "url": "https://eu-central-1-shared-euc1-02.graphassets.com/cmbq7mmxm0nhp07uqduk/cmcaodlnt9vxpfhycpf4", "fileName": "content-finder.gif" } ] } } ``` 2. Combine the `url` and `fileName` manually or via a script, separated by `/`. The final URL would look like this: ```url https://eu-central-1-shared-euc1-02.graphassets.com/cmbq7mmxm0nhp07uqduk/cmcaodlnt9vxpfhycpf4/content-finder.gif ``` Ensure that the file name and file extension (`jpg`, `webp`, etc.) matches the current version of the asset. If you convert a `jpg` to `webp` using URL transformations, then you need to update the extension at the end to `webp`. --- # Localized assets Source: https://hygraph.com/docs/api-reference/assets/localized-assets If you want to find out which asset system your project uses and which section of this document applies to you, [click here](/docs/api-reference/assets/assets-overview#which-asset-system-does-my-project-use). ## Hygraph Asset Management Our asset system lets you create a localizations by providing as `fileName` (optional) and `uploadUrl`. You can use the `createAsset` mutation and create the localization simultaneously, or use `updateAsset` and add a localization to an existing asset. If you don't pass a `fileName`, the system uses the file name of the file you provide - via URL or local file. If you pass a `fileName`, it overwrites the name of the file you provide. The following example shows the URL upload of an asset along with its localized content: ```graphql mutation test { createAsset( data: { uploadUrl: "" localizations:{ create: { locale: de data:{ uploadUrl: "" } } } } ) { id url localizations { id url } } } ``` This other example updates an existing asset entry, adding the `de` localization: ```graphql mutation test { updateAsset( where: {id: ""} data: { localizations: { create: { locale: de, data: { uploadUrl: "" } } } } ) { id locale url localizations { url locale } } } ``` For the action of creation of localized assets to work, your project configuration must already have the locale configured. Learn more about [mutating localized content](/docs/api-reference/content-api/localization#mutating-localized-content). ## Legacy asset system Since assets are localized by default, you can upload a file for your project locales. You can do this from the UI: Simply navigate to **Assets** and access the edit view of your asset entry. Click the eye icon next to your localization to ensure the `Upload` button is visible. ![Asset localization - Legacy asset system](/images/docs/api-reference/assets/asset-localization-legacy.png) Localizations only display on the sidebar if you've previously configured them in your project settings. ## Localize assets in the UI You can also localize assets using the UI. [Check out this document to learn more](/docs/developer-guides/content/content-localization#adding-a-translation). --- # Publishing assets Source: https://hygraph.com/docs/api-reference/assets/publishing-assets Since **Assets** is a system model, assets also come with publishing capabilities. You can use GraphQL mutations to publish or unpublish your assets, to and from content stages. ```graphql # Use the publishAsset mutation along with your asset ID and a destination stage to publish an asset to that stage # This example publishes an asset to the PUBLISHED stage mutation { publishAsset(where: {id: ""}, to: PUBLISHED) { id } } ``` ```graphql # Use the unpublishAsset mutation along with an asset ID to return the asset to the DRAFT stage mutation { unpublishAsset(where: {id: ""}) { id } } ``` Learn more about [publishing to content stages](/docs/api-reference/content-api/content-stages). --- # Referencing assets Source: https://hygraph.com/docs/api-reference/assets/referencing-assets While you can query for individuals assets, or fetch all and filter, order, and paginate, assets are best used when related to another model. When extending the Hygraph schema with your own models, the [Asset](/docs/api-reference/schema/field-types#asset) field type exposes all of the available transformations below. For example, imagine you have a `Product` model with a [one to many](/docs/api-reference/schema/field-types#one-to-many) reference to assets, as `productImage`: ```graphql query MyQuery { product(where: {productSlug: "green-hoodie"}) { productImage { fileName height width size } } } ``` ```graphql { "data": { "product": { "productImage": [ { "fileName": "green hoodie 3.jpg", "height": 3862, "width": 5856, "size": 4324263 }, { "fileName": "green hoodie 1.jpg", "height": 6720, "width": 4480, "size": 4989479 }, { "fileName": "green hoodie 2.jpg", "height": 6720, "width": 4480, "size": 4996739 } ] } } } ``` You can query any of the [system fields](/docs/api-reference/schema/system-fields#asset-fields) here as well. --- # Asset transformations Source: https://hygraph.com/docs/api-reference/assets/transformations When fetching assets, you can pass an optional `transformation` argument to the `url` field. Hygraph enforces [transformation safeguards](#transformation-safeguards) on requests and capacity. Breaches return `400 Bad Request` or `429 Too Many Requests`, depending on the safeguard. If you want to find out which asset system your project uses and which section of this document applies to you, [click here](/docs/api-reference/assets/assets-overview#which-asset-system-does-my-project-use). ## Hygraph Asset Management ### Auto image This document transformation uses the `auto_image` parameter to determine which [mimetype](/docs/api-reference/assets/transformations#file-type-conversion) to serve based on the browsers preference. | Arg | Description | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `auto_image` | Determines which mimetype to serve based on the browsers preference. We either send back a `webp` or an `avif`, depending on the **Accept** request header. Without an **Accept** header, we use `jpg` as fallback. | For example: ```graphql { assets { url( transformation: { document: {output: {format: autoImage}} } ) } } ``` ```graphql { "data": { "assets": [ { "url": "https://REGION.graphassets.com/ENV_ID/auto_image/HANDLE" } ] } } ``` ### Blur This transformation uses the `blur` parameter to blur your image. | Arg | Description | | -------- | ------------------------------------------------------------------ | | `amount` | Blur effect amount. The value must be an integer from `0` to `20`. | For example, we can query all assets, and blur images to an amount of 4: ```graphql { assets { url( transformation: { image: { blur: {amount: 4} } } ) } } ``` ```graphql { "data": { "assets": [ { "url": "https://REGION.graphassets.com/ENV_ID/blur=amount:4/HANDLE" } ] } } ``` ### Border This transformation uses the `border` parameter to control the width, color and background of your image borders. | Arg | Description | | ------------ | ------------------------------------------------------------------------------------------------------------------------------ | | `width` | Border width. The value must be an integer from `1` to `1000`. | | `color` | Border color. The value must be a hexadecimal or shortened hexadecimal color code. | | `background` | Border color. The value must be a hexadecimal or shortened hexadecimal color code, or a supported color name. [Here's a list of color names and their corresponding HEX values](/docs/api-reference/assets/border-hex-values) that you can use for your image borders. | For example, we can query all assets, and set the image borders using our chosen characteristics: ```graphql { assets { url( transformation: { image: { border: {width: 2, color: "gray15", background: "azure"} } } ) } } ``` ```graphql { "data": { "assets": [ { "url": "https://REGION.graphassets.com/ENV_ID/border=width:2,color:gray15,background:azure/HANDLE" } ] } } ``` ### Compress This transformation compresses PNG & JPG files. | Arg | Description | | ---------- | ------------------------------------------------------------------------------------------------ | | `compress` | Image compression. You can choose to compress the metadata as well by passing `true` or `false`. | - `compress` will only work on `jpg` and `png` file formats, otherwise it returns unchanged. - Make compress the last transformation in your chain for better results. For example, we can query all assets, and compress images along with their metadata: ```graphql { assets { url( transformation: { image:{compress:{metadata:true}} } ) } } ``` ```graphql { "data": { "assets": [ { "url": "https://REGION.graphassets.com/ENV_ID/compress=metadata:true/HANDLE" } ] } } ``` ### Crop This transformation uses the `crop` parameter to crop images by entering coordinates and crop dimensions: - The starting points for `X` and `Y` coordinates are [0,0], aligning with the top-left corner of the image. - The `width` and `height` parameters determine the size in pixels of the cropping rectangle. The output will include only the portion of the image within the designated crop area. | Arg | Description | | -------- | ---------------------------------------------------------------------------------------------- | | `x` | The `x` coordinate of the image. The value must be an integer from `0` to `10000`. | | `y` | The `y` coordinate of the image. The value must be an integer from `0` to `10000`. | | `width` | The width in pixels to resize the image to. The value must be an integer from `1` to `10000`. | | `height` | The height in pixels to resize the image to. The value must be an integer from `1` to `10000`. | For example, we can query all assets, and crop images: ```graphql { assets { url( transformation: { image:{crop:{ x: 100, y: 200, width:300, height: 400 }} } ) } } ``` ```graphql { "data": { "assets": [ { "createdAt": "2024-01-19T13:56:45.723049+00:00", "url": "https://REGION.graphassets.com/ENV_ID/crop=dim:[100,200,300,400]/HANDLE" } ] } } ``` ### Quality Use the `quality` parameter to set the quality of your image without risking generating a larger file. | Arg | Description | | ------- | ------------------------------------------------------------------------------- | | `value` | The quality value of the image. The value must be an integer from `1` to `100`. | - Only supported for the following formats: `jpeg`, `jpg`, `webp`, `gif`, `heif`, `tiff`, `avif` - You can use this transformation to reduce the file size of your image before a compress task. For example, we can query all assets, and set the image quality to 50: ```graphql { assets { url( transformation: { image: { quality: {value: 50} } } ) } } ``` ```graphql { "data": { "assets": [ { "url": "https://REGION.graphassets.com/ENV_ID/quality=value:50/HANDLE" } ] } } ``` ### Resize This transformation uses the `resize` parameter to adjust the image `height`, `width` and `fit`. The `image` takes the following arguments: | Arg | Type | Description | | -------- | ---------- | ------------------------------------------------------------------------------------------------------------- | | `width` | `Int` | The width in pixels to resize the image to. The value must be an integer from `1` to `10000`. | | `height` | `Int` | The height in pixels to resize the image to. The value must be an integer from `1` to `10000`. | | `fit` | `ImageFit` | The default value for this parameter is `clip`. Check the following table for all possible values. | The `ImageFit` takes one of the following values: | Value | Description | | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `clip` | Resizes the image to fit within the specified parameters without distorting, cropping, or changing the aspect ratio. | | `crop` | Resizes the image to fit the specified parameters exactly by removing any parts of the image that don't fit within the boundaries. | | `scale` | Resizes the image to fit the specified parameters exactly by scaling the image to the desired size. The aspect ratio of the image is not respected and the image can be distorted using this method. | | `max` | Resizes the image to fit within the parameters, but as opposed to `fit:clip` will not scale the image if the image is smaller than the output size. | Only supported for the following formats: `jpeg`, `jpg`, `png`, `gif`, `bmp` , `tiff`, `webp`, `avif` For example, we can query all assets, and resize images: ```graphql { assets { url( transformation: { image: { resize: { width: 50, height: 50, fit: clip } } } ) } } ``` ```graphql { "data": { "assets": [ { "url": "https://REGION.graphassets.com/ENV_ID/resize=fit:clip,height:50,width:50/HANDLE" } ] } } ``` ### Sharpen This transformation uses the `sharpen` parameter to sharpen your image. | Arg | Description | | -------- | --------------------------------------------------------------------- | | `amount` | Sharpen effect amount. The value must be an integer from `0` to `20`. | For example, we can query all assets, and sharpen images to an amount of 2: ```graphql { assets { url( transformation: { image: { sharpen: {amount: 2} } } ) } } ``` ```graphql { "data": { "assets": [ { "url": "https://REGION.graphassets.com/ENV_ID/sharpen=amount:2/HANDLE" } ] } } ``` ### File type conversion The following table shows the original file type you'd be changing from, and the possible formats you'd change it into. | Input type | Available output formats | Mimetype | | ---------- | -------------------------------------------------- | ----------------- | | `AVIF` | `JPG`, `PNG`, `WEBP`, `GIF`, `TIFF` | `image/avif` | | `BMP` | `JPG`, `PNG`, `SVG`, `WEBP`, `GIF`, `TIFF`, `AVIF` | `image/bmp` | | `GIF` | `JPG`, `PNG`, `SVG`, `WEBP`, `TIFF`, `AVIF` | `image/gif` | | `JPG` | `PNG`, `SVG`, `WEBP`, `GIF`, `TIFF`, `AVIF` | `image/jpg` | | `PDF` | `JPG`, `GIF`, `WEBP`, `TIFF`, `AVIF` | `application/pdf` | | `PNG` | `JPG`, `SVG`, `WEBP`, `GIF`, `TIFF`, `AVIF` | `image/png` | | `SVG` | `JPG`, `PNG`, `WEBP`, `GIF`, `TIFF`, `AVIF` | `image/svg+xml` | | `TIFF` | `JPG`, `PNG`, `SVG`, `WEBP`, `GIF`, `AVIF` | `image/tiff` | | `WEBP` | `JPG`, `PNG`, `SVG`, `GIF`, `TIFF`, `AVIF` | `image/webp` | For example, we can query all assets, and convert the images to JPG: ```graphql query Assets { assets { createdAt url(transformation: {document: {output: {format: jpg}}}) } } ``` ```graphql { "data": { "assets": [ { "createdAt": "2024-01-19T13:56:45.723049+00:00", "url": "https://REGION.graphassets.com/ENV_ID/output=format:jpg/HANDLE" } ] } } ``` ### URL transformations It is possible to do URL transformations without using the API. You can do this by using the regular URL of an asset and then placing the transformation syntax in it. You would place the transformation here: ``` https://REGION.graphassets.dev/ENV_ID//HANDLE ``` The following example uses `resize`: ``` https://REGION.graphassets.dev/ENV_ID/resize=width:400,height:400/HANDLE ``` You can also chain the transformations. The following example uses `resize` and `sharpen`: ``` https://REGION.graphassets.dev/ENV_ID/resize=width:400,height:400/sharpen=amount:2/HANDLE ``` ## Transformation safeguards Hygraph enforces safeguards on asset transformations to keep image processing fast and reliable for every project. Safeguards fall into two groups: - **Request safeguards** validate what a transformation URL asks for: output dimensions, multi-page output, and how many times an operation may repeat. Breaches return `400 Bad Request` with a body beginning `"Asset transformation safeguard exceeded:"`. - **Capacity safeguards** bound the work a transformation costs to produce: the resolution of the source asset, and how many *new* transformations a project may generate per second. | Safeguard | Limit | Applies to | Response | | --- | --- | --- | --- | | Resize width / height | `10000` px each | Requested output | `400` | | Multi-page output width / height | `2000` px each | Requested output | `400` | | Multi-page upscaling factor | `3.0x` | Requested output | `400` | | Repetitions per transformation kind | `3` | Transformation URL | `400` | | Source resolution | `100` MP (`100,000,000` px) | Source asset | `400` | | New transformations per second | `40` RPS shared / `80` RPS dedicated | Uncached transformations, per environment | `429` | Safeguards apply to both GraphQL `url(transformation: {...})` queries and direct URL transformations. Transformations already generated and cached are unaffected by capacity safeguards. ### Resize safeguards `resize.width` and `resize.height` are each capped at `10000` px. The value `10000` is accepted, any value above `10000` is rejected. For example, `resize=width:15000` returns `Asset transformation safeguard exceeded: resize width 15000 must not exceed 10000` ### Multi-page asset safeguards Multi-page assets include animated image formats and multi-page documents such as animated GIF, animated WebP, APNG, animated AVIF/HEIC/HEIF, multi-page TIFF, and PDF. The following safeguards apply to all multi-page assets: | Safeguard | Value | | ------------------ | -------- | | Maximum output width | `2000` px | | Maximum output height | `2000` px | | Maximum upscaling factor | `3.0x` | A request must satisfy all three safeguards. A request within the `2000` px dimension safeguards can still be rejected if it upscales the original by more than `3.0x`. Example response bodies: - `Asset transformation safeguard exceeded: multi-page width 3000 must not exceed 2000 for gif` - `Asset transformation safeguard exceeded: multi-page upscaling factor 15.00 exceeds 3.00 for avif` ### Per-transformation repetition cap Each transformation kind can appear at most `3` times in a single transformation URL. This safeguard is per kind, not per total transformation count. The counted kinds are: `resize`, `output`, `blur`, `sharpen`, `border`, `quality`, `crop`, `compress`, and `cache`. A URL containing four `resize` transformations returns `Asset transformation safeguard exceeded: transformation kind - resize with value 4 exceeded its default limit: 3` ### Source resolution A transformation decodes the entire source image into memory before touching it. File size is a poor predictor of that cost: a few-megabyte file can decode into a gigapixel image, which is not always intuitive the first time a small-looking upload trips this limit. Bounding resolution, rather than file size, is what reliably bounds the memory a single request can consume. So, Hygraph caps the resolution of the **source image** a transformation may be applied to at **100 megapixels** (100,000,000 pixels, for example 10,000 × 10,000). Resolution is measured as `width × height` of the decoded image, per page. Your plan's asset upload limit governs how large a file you can **store**. The source resolution cap governs what the image pipeline will transform. | Action | Asset above the source resolution cap | | --- | --- | | Uploading the asset | Allowed, up to your plan's upload limit | | Serving or downloading the original | Works normally | | Requesting a transformation of it | Returns `400 Bad Request` | If your plan allows, you can still store, version, publish, and download assets above the source resolution cap at their original URL. But you cannot transform these assets. For example, requesting a transformation of a 15,000 × 12,000 source image (180 MP) returns `Asset transformation safeguard exceeded: source image too large to transform: 180000000 pixels exceeds the 100000000 pixel limit` Animated and multi-page assets are measured one page at a time, so frame count doesn't count against the cap. Such assets are bounded separately by the [multi-page safeguards](#multi-page-asset-safeguards). #### Working with large source assets - **Resize once, at ingest.** Store a web-scale master and keep the full-resolution file in your DAM or archive. - **Serve the original where the full resolution matters.** The unmodified asset URL has no resolution cap. You can use it for download links, print assets, and archival access. - **Re-upload a downscaled version** if you need transformations of an existing over-limit asset. ### Transformation rate limit Generating a transformation that does not exist yet is a compute-intensive operation. To protect throughput across all projects, Hygraph applies a **requests-per-second (RPS) limit on new transformations**, counted per project environment. | Deployment | New transformations per second, per environment | | --- | --- | | Shared clusters | 40 RPS | | Dedicated clusters | 80 RPS | The limit applies **only** when Hygraph has to generate a transformation it has not produced before. It does not apply to: - **Cached transformations**: Any transformation already generated is served from the CDN with no limit and no rate-limit accounting. - **Original asset requests**: Fetching the unmodified asset is never rate-limited. In practice, this means production traffic is effectively unlimited: a page serving the same set of image variants to thousands of visitors generates one transformation per variant, then serves cached results indefinitely. Unlike the request safeguards, a rate-limited response body does not begin with `"Asset transformation safeguard exceeded:"`. Detect it by the `429` status code. For example, when an environment exceeds its allowance for new transformations, it returns `429 Too Many Requests` with a `Retry-After` header. The `Retry-After` header gives the number of seconds to wait before retrying. The limit is measured over a one-second window, so allowances recover immediately. A short backoff and retry is the correct client behavior. #### Staying within the limit The limit counts *distinct new* transformations. Increase your cache hit rate to avoid being rate-limited. - **Reuse a fixed set of variants.** Pick the sizes your layout needs (thumbnail, card, hero, and their 2× versions) and reuse those exact URLs everywhere. Repeat requests hit the cache. - **Don't size images from runtime values.** Viewport width, device pixel ratio, or container size produce a near-unique URL per visitor, so each one triggers a new transformation. Use a fixed set of breakpoints instead. - **Warm variants before a launch.** Request each new variant once, before a campaign, migration, or bulk import goes live. Don't let peak traffic generate them for the first time. - **Throttle bulk jobs.** When transforming a whole asset library, limit concurrency and retry on `429` instead of firing every request at once. ## Validate transforms We provide a validation field you can enable to check the combination of transform arguments are valid. For example, you may query a video, and request to change the file type to PDF. `validateOptions: true` will warn you that this is not allowed. ```graphql { assets { url( transformation: { document: { output: { format: pdf } } validateOptions: true } ) } } ``` ## Combine transforms It is possible to combine both transformation arguments: ```graphql { assets { url( transformation: { image: { resize: { width: 50, height: 50, fit: clip } } document: { output: { format: png } } validateOptions: true } ) } } ``` ## Alias transforms [GraphQL aliases](/docs/api-reference/content-api/queries#combining-queries) are great for querying the same asset URL with multiple transformations. For example, you could transform product images to include a `thumbnail`. ```graphql { products { images { thumbnail: url( transformation: { image: { resize: { width: 50, height: 50, fit: clip } } document: { output: { format: png } } } ) url(transformation: { document: { output: { format: png } } }) } } } ``` ## Legacy asset system ### Resize images The `image` takes the following arguments: | Arg | Type | Description | | -------- | ---------- | ---------------------------------------------------------------------------------------------- | | `width` | `Int` | The width in pixels to resize the image to. The value must be an integer from `1` to `10000`. | | `height` | `Int` | The height in pixels to resize the image to. The value must be an integer from `1` to `10000`. | | `fit` | `ImageFit` | The default value for the fit parameter is `clip`. | The `ImageFit` takes one of the following values: | Value | Description | | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `clip` | Resizes the image to fit within the specified parameters without distorting, cropping, or changing the aspect ratio. | | `crop` | Resizes the image to fit the specified parameters exactly by removing any parts of the image that don't fit within the boundaries. | | `scale` | Resizes the image to fit the specified parameters exactly by scaling the image to the desired size. The aspect ratio of the image is not respected and the image can be distorted using this method. | | `max` | Resizes the image to fit within the parameters, but as opposed to `fit:clip` will not scale the image if the image is smaller than the output size. | For example, we can query all assets, and resize images: ```graphql { assets { url( transformation: { image: { resize: { width: 50, height: 50, fit: clip } } } ) } } ``` ### Convert file type Depending on the asset type you're dealing with, it's possible to transform the `output` to another file type by passing a `format` value. | Current file type | Available output formats | | ----------------- | ---------------------------------------------------------------- | | PDF | `jpg`, `odp`, `ods`, `odt`, `png`, `svg`, `txt`, `webp` | | DOC | `docx`, `html`, `jpg`, `odt`, `pdf`, `png`, `svg`, `txt`, `webp` | | DOCX | `doc`, `html`, `jpg`, `odt`, `pdf`, `png`, `svg`, `txt`, `webp` | | ODT | `doc`, `docx`, `html`, `jpg`, `pdf`, `png`, `svg`, `txt`, `webp` | | XLS | `jpg`, `pdf`, `ods`, `png`, `svg`, `xlsx`, `webp` | | XLSX | `jpg`, `pdf`, `ods`, `png`, `svg`, `xls`, `webp` | | ODS | `jpg`, `pdf`, `png`, `xls`, `svg`, `xlsx`, `webp` | | PPT | `jpg`, `odp`, `pdf`, `png`, `svg`, `pptx`, `webp` | | PPTX | `jpg`, `odp`, `pdf`, `png`, `svg`, `ppt`, `webp` | | ODP | `jpg`, `pdf`, `png`, `ppt`, `svg`, `pptx`, `webp` | | BMP | `jpg`, `odp`, `ods`, `odt`, `pdf`, `png`, `svg`, `webp` | | GIF | `jpg`, `odp`, `ods`, `odt`, `pdf`, `png`, `svg`, `webp` | | JPG | `jpg`, `odp`, `ods`, `odt`, `pdf`, `png`, `svg`, `webp` | | PNG | `jpg`, `odp`, `ods`, `odt`, `pdf`, `png`, `svg`, `webp` | | WEBP | `jpg`, `odp`, `ods`, `odt`, `pdf`, `png`, `svg`, `webp` | | TIFF | `jpg`, `odp`, `ods`, `odt`, `pdf`, `png`, `svg`, `webp` | | AI | `jpg`, `odp`, `ods`, `odt`, `pdf`, `png`, `svg`, `webp` | | PSD | `jpg`, `odp`, `ods`, `odt`, `pdf`, `png`, `svg`, `webp` | | SVG | `jpg`, `odp`, `ods`, `odt`, `pdf`, `png`, `webp` | | HTML | `jpg`, `odt`, `pdf`, `svg`, `txt`, `webp` | | TXT | `jpg`, `html`, `odt`, `pdf`, `svg`, `webp` | For example, let's transform all assets to PDFs: ```graphql { assets { url(transformation: { document: { output: { format: pdf } } }) } } ``` --- # Updating assets Source: https://hygraph.com/docs/api-reference/assets/updating-assets ## Overview Since assets are a system model, and automatically added to every project, you can extend them with your own custom fields. These fields can be updated using GraphQL mutations. Make sure you read our [webhooks documentation](/docs/api-reference/basics/webhooks#webhooks-and-assets) to understand how they work with the Hygraph Asset Management System. ## Update metadata In the following example, we have added `altText` and `caption` fields to our **Assets** model. Here, we are using an `updateAsset` mutation to change the text in them: ```graphql mutation { updateAsset( where: {id: ""} data: {caption: "this is a new caption", altText: "this is new alternative text"} ) { id } } ``` ```graphql { "data": { "updateAsset": { "id": "" } } } ``` After successfully running this mutation, if you access your asset in edit mode, you will see the new `altText` and `caption`. Learn more about [Mutations](/docs/api-reference/content-api/mutations#update-entries). ## Update existing asset The Hygraph Asset Management System offers a way to update an existing asset entry and reuploading a new file for it. To do this, there is a route to do a file upload, or a remote URL upload. ### Update via remote URL Use `reUpload: true` as follows: ```graphql mutation test { updateAsset( where: {id: ""} data: {reUpload: true, uploadUrl: ""} ) { id upload { status expiresAt error { code message } } } } ``` ### Update via file upload To update an asset via file upload you need to follow the [asset upload async process](/docs/api-reference/assets/uploading-assets#upload-by-file), with the data you get from `requestPostData` to send the local file to S3. Use `reUpload: true` as follows: ```graphql mutation test { updateAsset(where: {id: ""}, data: {reUpload: true}) { id upload { status expiresAt error { code message } requestPostData { url date key signature algorithm policy credential securityToken } } } } ``` --- # Uploading assets Source: https://hygraph.com/docs/api-reference/assets/uploading-assets If you want to find out which asset system your project uses and which section of this document applies to you, [click here](/docs/api-reference/assets/assets-overview#which-asset-system-does-my-project-use). Our asset system uses AWS S3's pre-signed upload URLs and includes the upload action into the GraphQL API. This process will cover the following steps: 1. Send a `createAsset` mutation passing some basic information about the asset. This also works as a nested mutation. 2. You can [upload by file](/docs/api-reference/assets/uploading-assets#upload-by-file) or [by URL](/docs/api-reference/assets/uploading-assets#upload-by-remote-url). The upload works differently depending on your choice. 3. As soon as that the upload complete, the asset entry will switch from an internal `ASSET_CREATE_PENDING` state to `ASSET_CREATE_COMPLETE`, and the asset will be served via its URL. Make sure you read our [webhooks documentation](/docs/api-reference/basics/webhooks#webhooks-and-assets) to understand how they work with the Hygraph Asset Management System. ### Upload by file This is a two-step process: 1. Create the asset via GraphQL mutation. This returns a URL that needs to be called to execute the upload. 3. Use that pre-signed URL to upload the asset file. The asset stays pending until the file gets successfully uploaded and analysed. Size limits for uploaded files depend on the plan. Check out our pricing page. #### Create asset via GraphQL mutation The first step is to create the actual asset. If you don't pass a `fileName`, the system uses the file name of the file you provide - via URL or local file. If you pass a `fileName`, it overwrites the name of the file you provide. You will need to add it to `data`. Here's a sample mutation to create an asset called `test.jpg`, and the JSON response: ```graphql mutation createAsset { createAsset(data: {}) { id url upload { status expiresAt error { code message } requestPostData { url date key signature algorithm policy credential securityToken } } } } ``` ```json { "data": { "createAsset": { "id": "clt47n0t600j807vvirlzi1xx", "url": "https://eu-central-1.graphassets.com/clpqzrnm4007e01t810b59ir4/clt47n0t600j907vveibipmov", "upload": { "status": "ASSET_CREATE_PENDING", "expiresAt": "2024-02-27T12:38:49+00:00", "error": null, "requestPostData": { "url": "https://eu-1-assets-delivery-hg75hf.s3.eu-central-1.amazonaws.com", "date": "20240227T101349Z", "key": "clpqzrnm4007e01t810b59ir4/upload/clpqzrnh4001q01t84tl21zdl/clpqzrnh6001u01t866zfftoa/clt47n0t600j907vveibipmov/${filename}", "signature": "c17e7b1c5d4af665a8fc74421fae53b72e94bb19e85e7befd1eb79b865bef7d2", "algorithm": "AWS4-HMAC-SHA256", "policy": "eyJleHBpcmF0aW9uIjoiMjAyNC0wMi0yN1QxMjozODo0OS45MTRaIiwiY29uZGl0aW9ucyI6W3siYnVja2V0IjoiZGV2LTEtYXNzZXRzLWRlbGl2ZXJ5LWY3OGM1YjUifSx7ImtleSI8ImNscHF6cm5tNDAwN2UwMXQ4MTBiNTlpcjQvdXBsb2FkL2NscHF6cm5oNDAwMXEwMXQ4NHRsMjF6ZGwvY2xwcXpybmg2MDAxdTAxdDg2NnpmZnRvYS9jbHQ0N24wdDYwMGo5MDd2dmVpYmlwbW92LyR7ZmlsZW5hbWV9In0seyJ7LWFtei1hbGdvcml0aG0iOiJBV1M0LUhNQUMtU0hBMjU2In0seyJ4LWFtei1jcmVkZW50aWFsIjoiQVNJQVZRUkUzVk1FV0dZNUMyWEwvMjAyNDAyMjcvZXUtY2VudHJhbC0xL3MzL2F3czRfcmVxdWVzdCJ9LHsieC1hbXotZGF0ZSI6IjIwMjQwMjI3VDEwMTM0OVoifSx7IngtYW16LXNlY3VyaXR5LXRva2VuIjoiSVFvSmIzSnBaMmx1WDJWakVPci8vLy8vLy8vLy93RWFER1YxTFdObGJuUnlZV3d0TVNKSE1FVUNJRVppa0hoNmIrM1lZWWJyU2xFaWtpZWwvVnZZdCs0SEpWTE5QcFFzZ0tibkFpRUFnUldLdURxR3AzRUJic3A2MTRFUTZwMGs2RnoweS9mdDhLRk1USTltUmVzcS9RTUkwLy8vLy8vLy8vLy9BUkFGR2dadd3ek56a3hNRGd4TnpRMk1ERWlERTJPSWF5RDNEQXI3cE9OSVNyUkExYk1haXdON1FZSW5SSjNuby8vY0F3cm0rbjg0SVZOdWsraER3QXR2SG54aWxDcCtNZktlMVh6MTN1TWhrTWNGa1c2T2NGZXF1R09lUHJYV28yNjdEaXlKRkE0V0dqU3dxem8xVFRWUjhJTkZoaUJlWmdxa2Q4bEtZQnVRYzBsbWxIekhLamk1WVloYStVZG45L1BrRit3dlp5ZFF6Qk53NzJzakEwT2QrakxaUEhuR29kK2Z3WjFUT2o5RE4zd3NQb2E0MTU3OFBvU1ZSRzB1ZGN4RGFHejVxeFh6OXZvb2cwQ3pTVlFiUk1tNW1UTHdSelp3ZlJYMTQ0U2NxMUsrYVNMVE9UdW1mYzROTjFzdlVEODZ0VU51azFGVUxzOE5FZi9PcFlhZmcwMkhyWkEvRHlDSnhlS3A1SkRxRndCMGJZL2lUcTRqS1VLOFBDQXF1T3Q2bFRQRWRTTFJhTnVBSW43cWd6UFNmRi8rQ3JjREtEblZTWmlNR3VhSlA1cmhjUlRob01XdHFjKzdiL3pPNjhMNVdrL0o5SGtmZzNjNmh5UGlsTkRhMG1RY0lhU0VzT0dFTkhQL2Q5K2xJSUhJZWJXNDFmT3VMSVltWEJlaWVGTzFYNDRmbk5Ld0F2bDlkNkxmQTZxdiswNlJtSFdRYkg5VjdLNmRiOHp4ZDNYMDFXRldTckVKdEFEU2xRNkdvejdNRmtIaVk2SnF2NEpjWnJlV1pVTXhFSTgydEVRWWR6R3lwdmh1ZEd6TDhJR3k4QmpjcnFQakhMWjRRUTRMaW9hcS95czNrM1BSZ1QwcEtIT0RMNm5jK1E2cmpDUTR2YXVCanFsQWQ2VmxidGxvYXpFWlc1SUhqRGFVYTk3VjFwV3UxamxVTlN0dDNLNVZmeDJRWlNtdXdUdDl3bitQYzJZL0xpMDExU0pSYWZCZy9oQlhmMWpDTG4yZ2lLMkFuMEovWGxnZ3FQSmdUYnhydS83RThsTFdmOG9uL1g4aVc1V1AxNDZOMjVaZk0xdXh0RGNQNTB5eWhGemFaOGRjR1p3c1FFRjY3b01mNSsxN3JiVC9kT2k3WkE1RTFLVG4vbmJMZUVhcEhZckQ5SkJESUtTZjRyaWhyLzkzcUtYTjFWajh3PT0ifV19", "credential": "AFDGAVRRE3VMFWGY5C2XL/20240227/eu-central-1/s3/aws4_request", "securityToken": "IQoJb3JpZ2luX2VjEOr//////////wEaDGV1LWNlbnRyYWwtMSJHMEUCIEZikHh6b+3YYYbrSlEikiel/VvYt+4HJVLNPpQsgKbnAiEAgRWKuDqGp3EBbsp614EQ6p0k6Fz0y/ft8KFMTI9mResq/QMI0///////////ARAFGgwzNzkxMDgxNzQ2MDEiDE2OIayD3DAr7pONISrRA1bMaiwN7QYInRJ3no//cAwrm+n84IVNuk+hDwAtvHnxilCp+MfKe1Xz13uFhiBeZgqkd8lKYBuQc0lmlHzHKji5n9/PkF+wvZydQzBNw72sjA0Od+jLZPHnGod+fwZ1TOj9DN3wsPoa41578PoSVRG0udcxDaGz5qxXz9voog0CzSVQbRMm5mTLwRzZwfRX144Scq1K+aSLTOTumfc4NN1svUD86tUNuk1FULs8NEf/OpYafg02HrZA/DyCJxesdgsdgerKp5JDqFwB0bY/iTq4jKUK8PCAquOt6lTPEdSLRaNuAIn7qgzPSfF/+CrcDKDnVSZiMGuaJP5rhcRThoMWtqc+7b/zO68L5Wk/J9Hkfg3c6hyPilNDa0mQcIaSEsOGENHP/d9+lIIHIebW41fOuLIYmXBeieFO1X44fnNKwAvl9d6LfA6qv+06RmHWQbH9V7K6db8zxd3X01WFWSrEJtADSlQ6y8BjcrqPjHLZ4QQ4Lioaq/ys3k3PRgT0pKHODL6nc+Q6rjCQ4vauBjqlAd6VlbtloazEZW5IHjDaUa97V1pWu1jlUNStt3K5Vfx2QZSmuwTt9wn+Pc2Y/Li011SJRafBg/hBXf1jCLn2giK2An0J/XlggqPJgTbxru/7E8lLWf8on/X8iW5WP146N25ZfM1uxtDcP50yyhFzaZ8dcGZwsQEF67oMf5+17rbT/dOi7ZA5E1KTn/nbLeEapHYrD9JBDIKSf4rihr/93qKXN1Vj8w==" } } } } } ``` Regarding the expiry of the `requestPostData` information: You can use the `expiresAt` field for this. If you use the `requestPost` info after that date, it will fail. #### Upload asset We will use curl for this example. Imagine we extracted the `data.createAsset.upload.requestPostData` subkeys into variables, and we have a file `test.jpg` in our current working directory: ```bash URL="https://dev-1-assets-delivery-f78c5b5.s3.eu-central-1.amazonaws.com" DATE="20240123T173451Z" KEY="clr7o58jb00w701vwhpc69xda/upload/clr7o587k000j01uhul69p0rb/clr7o58ca00qi01vw7wzoguau/clrqmzdpn00id0bvucez7gnm9" SIGNATURE="3f812cc0b05d59ee3d1efdd9dc046ed6edf595749c2763b85b923133a84e8d86" ALGORITHM="AWS4-HMAC-SHA256" POLICY="eyJleHBpcmF0aW9uIjoiMjAyNC0wMS0yM1QxOTo1OTo1MS45NDdaIiwiY29uZGl0aW9ucyI6W3siYnVja2V0IjoiZGV2LTEtYXNzZXRzLWRlbGl2ZXJ5LWY3OGM1YjUifSx7ImtleSI6ImNscjdvNThqYjAwdzcwMXZ3aHBjNjl4ZGEvdXBsb2FkL2NscjdvNTg3azAwMGowMXVodWw2OXAwcmIvY2xyN281OGNhMDBxaTAxdnc3d3pvZ3VhdS9jbHJxbXpkcG4wMGlkMGJ2dWNlejdnbm05In0seyJ4LWFtei1hbGdvcml0aG0iOiJBV1M0LUhNQUMtU0hBMjU2In0seyJ4LWFtei1jcmVkZW50aWFsIjoiQVNJQVZRUkUzVk1FMkJHWVZLT1AvMjAyNDAxMjMvZXUtY2VudHJhbC0xL3MzL2F3czRfcmVxdWVzdCJ9LHsieC1hbXotZGF0ZSI6IjIwMjQwMTIzVDE3MzQ1MVoifSx7IngtYW16LXNlY3VyaXR5LXRva2VuIjoiSVFvSmIzSnBaMmx1WDJWakVLci8vLy8vLy8vLy93RWFER1YxTFdObGJuUnlZV3d0TVNKSE1FVUNJRUF6SFQ2OUdEWm9CcXp5Nkw4bExDUDFEZnNBZ3FaM0phRms0M3ZvcGNGRUFpRUFrVTljUVNTSTFCRmV5TVZjTEI1QkFrVzg3NUE2M2hkWTVnNDgxcEY4QitNcTlBTUlZeEFGR2d3ek56a3hNRGd4TnpRMk1ERWlERkxzOHB2a0JHRU9aV3dSZVNyUkF4amJvVXRobDRtRUM3VHVzTHpFcXc1T1JUOUZRSStYYVVQWXZRZi9ST3ZNOExBVkFYVHQwMWZHT0JyUkVRSDRDckJLSVVtMDNtQWFPd05nQ2g1aGFlTDhmTzZvRUJ3aTFLa2ZuRWpNWmtQcVJma0krMDZhK0o0SVBYTmR0bEt6b0VVOVd4Uk9TMzEyTC8rMjBpUkpLNmNoWmhhK04zVXNJMzRYR0U0L2IzNzV0aXpsWUx0RHJYN0JiMXFIdFVNYlg3VWVUaVJaSDU4S1VwRjloK2QyWWk2bUhuL1lGcmJVaGpOcjVGL1pkL2FkdU5nOGpKU3liVkw5bTVZdWQvdlI3enMyNXdpYlpJK3BOQjFXcVZzRWtiN09EN2g1QjFlQkE5dnNMdzBKdVlFMmFnYm0xNjF0SjZVS3A5Tkd4YmVWaFM1TjQ5N2txV2MzN3pCT3ZxdWxUU1JHUW04V1QrNGx1YzZyd2VTT0lFeUxMaFowT1ZCSWM1Q0Nrc1YvaFAxT2dxeUdSNGdKTDlwa3RhUDl0encxaFhyUHJhUW1mRHhoaVgrUStaNitkSWFxalY2VGlwcVhqTk9jM2tFSFIwNmpydnlhOVFsRGxXaWd2eGRaTHkwZktzQW8vR0dBTlhlM2ZGSzhVclVObE4ySHFGeHpBYU9JNEJPMmU1VE1sUzA5MTZZTFZUN1Zxd3hHOHQ0NTlUTUFLcXpJMTJ4N2JTbHFYQ0lHUlc1anlsazhaTE1mbGRZS2t6eDN6dWNKZDYvYVN4Q1c1bE1pa2lGRjYrbW9WQW9kck1qeGhLZW42QnNRZ3ZMVW1kamFmaVROMkRERDhMK3RCanFsQVRzWGdYUmdZOUVJQzJMUXEyOU0xSGRlbmViY3I5YU5JbmFBTVFKRlpMUnl1bnM3dzk0MnhjaW1sSTYyak15RWFUczRZM3M3aTMwYVpqc1E2ampMK1hKMVJTQW5Ta2lndFJZbHVKSGFlaWJkRDMydEZDamZhY3ovZWF5eVhGcEgzekc5NHJRZUk1V0ZBWVZXRFZyWEN2WmJROG9SejV4OHN5ZFBBZWROR1VEOHYrMk5XWDc0NTBTcXdXc2Jta0dlekxoZ0pzNkxyOVhSSG5rTEljdGFoUDVoazg0Q3p3PT0ifV19" CREDENTIAL="ASIAVQRE3VME2BGYVKOP/20240123/eu-central-1/s3/aws4_request" SECURITY_TOKEN="IQoJb3JpZ2luX2VjEKr//////////wEaDGV1LWNlbnRyYWwtMSJHMEUCIEAzHT69GDZoBqzy6L8lLCP1DfsAgqZ3JaFk43vopcFEAiEAkU9cQSSI1BFeyMVcLB5BAkW875A63hdY5g481pF8B+Mq9AMIYxAFGgwzNzkxMDgxNzQ2MDEiDFLs8pvkBGEOZWwReSrRAxjboUthl4mEC7TusLzEqw5ORT9FQI+XaUPYvQf/ROvM8LAVAXTt01fGOBrREQH4CrBKIUm03mAaOwNgCh5haeL8fO6oEBwi1KkfnEjMZkPqRfkI+06a+J4IPXNdtlKzoEU9WxROS312L/+20iRJK6chZha+N3UsI34XGE4/b375tizlYLtDrX7Bb1qHtUMbX7UeTiRZH58KUpF9h+d2Yi6mHn/YFrbUhjNr5F/Zd/aduNg8jJSybVL9m5Yud/vR7zs25wibZI+pNB1WqVsEkb7OD7h5B1eBA9vsLw0JuYE2agbm161tJ6UKp9NGxbeVhS5N497kqWc37zBOvqulTSRGQm8WT+4luc6rweSOIEyLLhZ0OVBIc5CCksV/hP1OgqyGR4gJL9pktaP9tzw1hXrPraQmfDxhiX+Q+Z6+dIaqjV6TipqXjNOc3kEHR06jrvya9QlDlWigvxdZLy0fKsAo/GGANXe3fFK8UrUNlN2HqFxzAaOI4BO2e5TMlS0916YLVT7VqwxG8t459TMAKqzI12x7bSlqXCIGRW5jylk8ZLMfldYKkzx3zucJd6/aSxCW5lMikiFF6+moVAodrMjxhKen6BsQgvLUmdjafiTN2DDD8L+tBjqlATsXgXRgY9EIC2LQq29M1Hdenebcr9aNInaAMQJFZLRyuns7w942xcimlI62jMyEaTs4Y3s7i30aZjsQ6jjL+XJ1RSAnSkigtRYluJHaeibdD32tFCjfacz/eayyXFpH3zG94rQeI5WFAYVWDVrXCvZbQ8oRz5x8sydPAedNGUD8v+2NWX7450SqwWsbmkGezLhgJs6Lr9XRHnkLIctahP5hk84Czw==" curl --request POST \ --url $URL --form X-Amz-Date=$DATE --form key=$KEY --form X-Amz-Signature=$SIGNATURE --form X-Amz-Algorithm=$ALGORITHM --form policy=$POLICY --form X-Amz-Credential=$CREDENTIAL --form X-Amz-Security-Token=$SECURITY_TOKEN --form file=@./test.jpg ``` You must put the `file` as last form entry. All other form entries must come before it. The system also allows you to reupload a file for an existing asset entry. [Click here to know more.](/docs/api-reference/assets/updating-assets#update-existing-asset) ### Upload by remote URL You can upload assets by remote URL in the GraphQL API by passing the URL of an asset hosted somewhere publicly accessible in the `createAsset` mutation. ```graphql mutation test { createAsset( data: { uploadUrl:"https://images.unsplash.com/photo-1682687218147-9806132dc697" } ) { id url } } ``` As this is an asynchronous process, the image may still become `PENDING` until fully uploaded. The system also allows you to reupload a file for an existing asset entry. [Click here to know more.](/docs/api-reference/assets/updating-assets#update-existing-asset) ## Legacy asset system Hygraph supports uploading assets via HTTP. You'll need a [Permanent Auth Token](/docs/api-reference/basics/authorization#permanent-auth-tokens) with **Mutations** API access enabled to upload by file, or URL. Assets are treated just like any other content entry, so they are automatically bound to the [environment](/docs/api-reference/basics/environments), and [authorization](/docs/api-reference/basics/authorization) settings of your project. - You must append `/upload` to your project API endpoint when uploading assets. For example, `https://[region].hygraph.com/v2/[projectId]/[environment]/upload`. - You can upload assets to your project without the need of a [Permanent Auth Token](/docs/api-reference/basics/authorization#permanent-auth-tokens) by adding Read and Create permissions to the Content API. **This is however unsafe and not advised**, as exposing the endpoint anywhere - like your website - while having any write access on the Public API, would essentially allow anyone to modify your data. ### Upload by file (Legacy) Size limits for uploaded files depend on the plan. Check out our pricing page. ```bash curl -XPOST -H "Authorization: Bearer {YOUR_PAT_VALUE}" -F fileUpload=@picture.jpg https://[region].hygraph.com/v2/[projectId]/[environment]/upload ``` ```js // Your file must have the .mjs extension because node-fetch is an ESM-only package. import fetch, { FormData, fileFrom } from 'node-fetch'; const form = new FormData(); form.set('fileUpload', await fileFrom('path/to/file.png')); fetch(`${process.env.HYGRAPH_URL}/upload`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.HYGRAPH_ASSET_TOKEN}`, }, body: form, }) .then((res) => res.json()) .then((data) => console.log(JSON.stringify(data, null, 2))) .catch((err) => console.log(err)); ``` ```json { "filename": "pexels-photo-1170986.jpeg", "mimetype": "image/jpeg", "size": 32476, "width": 500, "height": 750, "url": "https://media.graphassets.com/P3TkBzxyQLupgDWNFydB", "id": "ckfdz530o0001ip92cdr3bbmj" } ``` Here's also a standard JavaScript example: ```js const HYGRAPH_URL = ''; const HYGRAPH_ASSET_TOKEN = ''; async function upload() { const input = document.getElementById('fileUpload'); const file = input.files[0]; const form = new FormData(); form.append('fileUpload', file); // It is not recommended to use the HYGRAPH_ASSET_TOKEN in the Front-End. // In this example we're using it, but in a real application you should // use a backend to upload the file and use the HYGRAPH_ASSET_TOKEN there. const response = await fetch(`${HYGRAPH_URL}/upload`, { method: 'POST', headers: { Authorization: `Bearer ${HYGRAPH_ASSET_TOKEN}`, }, body: form, }); const data = await response.json(); console.log(JSON.stringify(data, null, 2)); } ``` ### Upload by remote URL (Legacy) You can also upload files by providing a remote URL, instead of a file. ```bash curl -XPOST -d url=https://media.graphassets.com/P3TkBzxyQLupgDWNFydB https://[region].hygraph.com/v2/[projectId]/[environment]/upload ``` ```js // Your file need to have the .mjs extension because node-fetch is an ESM-only package. import fetch from 'node-fetch'; fetch(`${process.env.HYGRAPH_URL}/upload`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.HYGRAPH_ASSET_TOKEN}`, 'Content-Type': 'application/x-www-form-urlencoded', }, body: `url=${encodeURIComponent( 'https://media.graphassets.com/P3TkBzxyQLupgDWNFydB' )}`, }) .then((res) => res.json()) .then((data) => console.log(JSON.stringify(data, null, 2))) .catch((err) => console.log(err)); ``` ```json { "filename": "pexels-photo-1170986.jpeg", "mimetype": "image/jpeg", "size": 32476, "width": 500, "height": 750, "url": "https://media.graphassets.com/J9BOEF3OSuuSXDhvriQa", "id": "ckgs20b80017109547vfev24c" } ``` --- # API Limits Source: https://hygraph.com/docs/api-reference/basics/api-limits API limits are technical safeguards that ensure your GraphQL API performs optimally and remains available for all users. These limits guard against common problems, such as big requests, inefficient queries, or high traffic, that could otherwise impact performance. We do not enforce any limits on requests that hit our [CDN cache](/docs/api-reference/basics/caching). API limits are enforced on all uncached GraphQL queries for shared regions. The rate limiting depends on the current load of the shared region and the subscription plan. These limits can be lifted on dedicated clusters and enterprise plans. You can [contact sales](/contact) to request a custom plan. ## Request size The maximum size of GraphQL queries and mutations, including the text and variables, as it reaches our API. This helps prevent oversized requests that could slow down your API. When you exceed this limit, you'll get a [`413 error`](/docs/api-reference/basics/errors#413-payload-too-large). | Plan | Limit | | ----- | ---------------------- | | Hobby |
  • Queries: 10 KB
  • Mutations: 30 KB
| | Growth |
  • Queries: 15 KB
  • Mutations: 70 KB
| | Enterprise |
  • Queries: 20 KB
  • Mutations: 80 KB
| Follow these steps to check your query/ mutation request size. 1. In your browser, go to **Developer Tools > Network**. 2. Run the GraphQL request in the [API Playground](/docs/api-reference/basics/api-playground). 3. Click on the request and check the **Request Payload** or **Size** column. 4. Compare the size against our limits. We recommend that you: - Test your queries against limits during development. - Break large queries into smaller, focused requests. For example: Instead of a query like this: ```graphql query DashboardData($userId: ID!) { user(id: $userId) { id name email avatar { url width height } settings { locale timezone notifications { marketing product updates security } } followers(first: 200) { nodes { id name avatar { url } } } posts(first: 50) { nodes { id title slug excerpt body coverImage { url width height } tags { id name } author { id name avatar { url } } comments(first: 200) { nodes { id body createdAt author { id name } } } } } notifications(first: 100) { nodes { id type message createdAt readAt } } } } ``` You can split it into three separate queries: ```graphql # Request 1: lightweight user profile query UserProfile($userId: ID!) { user(id: $userId) { id name avatar { url } settings { locale timezone } } } ``` ```graphql # Request 2: concise posts list for the dashboard query UserPostsList($userId: ID!) { user(id: $userId) { id posts(first: 20) { nodes { id title slug excerpt coverImage { url } } } } } ``` ```graphql # Request 3: fetch comments on-demand (example, when opening a post) query PostComments($postId: ID!) { post(id: $postId) { id comments(first: 20) { nodes { id body createdAt author { id name } } } } } ``` - Use GraphQL fragments to avoid repetition. For example: Instead of a query like this: ```graphql query ArticlePage($id: ID!) { post(id: $id) { id title author { id name username avatar { url width height } } lastEditor { id name username avatar { url width height } } comments(first: 20) { nodes { id body author { id name username avatar { url width height } } } } } me { id name username avatar { url width height } followers(first: 10) { nodes { id name username avatar { url width height } } } following(first: 10) { nodes { id name username avatar { url width height } } } } } ``` ```graphql fragment UserSummary on User { id name username avatar { url } } query ArticlePage($id: ID!) { post(id: $id) { id title author { ...UserSummary } lastEditor { ...UserSummary } comments(first: 20) { nodes { id body author { ...UserSummary } } } } me { ...UserSummary followers(first: 10) { nodes { ...UserSummary } } following(first: 10) { nodes { ...UserSummary } } } } ``` - Instead of relying on long and complex variable filters, use pagination to handle large data sets. For example: Instead of a query like this: ```graphql query BulkPosts($ids: [ID!]!) { posts(where: { id_in: $ids }, first: 5000) { nodes { id title excerpt author { id name } comments(first: 500) { nodes { id body author { id name } } } } } } ``` ```json { "ids": [ "post_000001", "post_000002", "post_000003", "post_000004", "post_000005", "... thousands more ids ...", "post_004999", "post_005000" ] // ≈ hundreds of kilobytes, just for variables → likely 413 (Payload Too Large) } ``` You can split it into: ```graphql # Request 1 ≈ a few bytes; repeat with {"after": ""} until hasNextPage=false query PagedPosts($first: Int! = 50, $after: String) { posts(first: $first, after: $after) { nodes { id title excerpt author { id name } } pageInfo { endCursor hasNextPage } } } ``` ```json { "first": 50, "after": null } ``` ``` # Request N ≈ Still tiny; fetch next pages on demand # Only when a user opens a post, also paginated query PagedComments($postId: ID!, $first: Int! = 25, $after: String) { post(id: $postId) { id comments(first: $first, after: $after) { nodes { id body author { id name } } pageInfo { endCursor hasNextPage } } } } ``` ```json { "postId": "post_000123", "first": 25, "after": null } ``` ```graphql # If you must target specific IDs, batch them. query BatchByIds($ids: [ID!]!) { posts(where: { id_in: $ids }) { nodes { id title excerpt } } } ``` ```json { "ids": ["post_000001", "... up to ~100 per batch ..."] } // Send multiple small requests instead of one huge list ``` We recommend that you do not: - Fetch unnecessary fields in your queries. - Create overly complex nested queries. - Ignore limit violation errors without addressing root causes. ## Requests per second The number of uncached requests you can send to the Content API per second. A single request can contain multiple queries and mutations. When you exceed this limit, you'll get a [`429 error`](/docs/api-reference/basics/errors#429-too-many-requests). This limit measures how many **requests start per second**. It is different from **concurrency operations**, which is measured by the number of operations running at the same time. | Plan | Limit | | ----- | ----------------------- | | Hobby | 5 req/sec | | Growth | 25 req/sec | | Enterprise | Up to 500 req/sec | ## Concurrent operations The number of uncached GraphQL operations (queries / mutations) that run simultaneously per environment. Multiple operations bundled in a single request count toward the concurrency limit. This helps prevent resource exhaustion during traffic spikes. When you exceed this limit, you'll get a [`429 error`](/docs/api-reference/basics/errors#429-too-many-requests). This limit measures **in-flight queries and mutations**. Even if your requests per second (RPS) rate is low, you can still exceed concurrency if many operations run at once. | Plan | Limit per environment | | ----- | ---------------------- | | Hobby |
  • Queries: 10
  • Mutations: 5
| | Growth |
  • Queries: 30
  • Mutations: 10
| | Enterprise |
  • Queries: 60
  • Mutations: 20
| To stay below the limit, count the total number of queries and mutations sent simultaneously, not just the number of fired requests. If a request contains five queries, it counts as five concurrent queries. You can also measure the total in-flight operations at any moment, by using a counter or concurrency control library in your script or code. We recommend that you: - Implement proper retry logic with [exponential backoff](#exponential-backoff) to handle temporary errors. - Distribute requests over time rather than sending large bursts at once. - Apply connection pooling and request queuing to manage load efficiently. - Monitor your application's concurrent request patterns. We recommend that you do not: - Retry immediately after receiving a 429 error. - Send large batches of requests simultaneously. - Ignore concurrent limit violations in your error handling. ## Requests per second vs. Concurrent operations These two limits measure different things: - Requests per second (RPS): How many new requests you start each second. - Concurrent operations: How many queries or mutations are running at the same time, even if they came from the same request. It’s possible to stay within one limit while exceeding the other. Always design your queries and requests to remain under both the RPS and concurrency limits. | Scenario | Request per second | Concurrent operations | Result| | ----- | ---------------------- |--------------- | -----------| |20 requests per second, each finishes in ~50 ms|
  • RPS = 20
  • Within Growth plan limit.
|
  • Concurrency ≈ 1 at any time, since requests complete quickly.
  • Within Growth plan limit.
| Both within limits.| |5 requests per second, each contains 10 queries. Each query takes ~2 seconds to finish.|
  • RPS = 5
  • Within Growth plan limit.
|
  • Concurrency = 50 (5 requests × 10 queries still running)
  • Exceeds Growth plan limit.
| Exceeds concurrency limit.| |40 requests sent at once, each with 1 query.|
  • RPS = 40
  • Exceeds Growth plan limit.
|
  • Concurrency = 40
  • Exceeds Growth plan limit.
| Exceeds both limits.| ## Handling API rate limits In this section, learn how to handle API rate limits with **Next.js**, **Gatsby**, and **Nuxt**. ### Next.js #### Thread limiting You can use this experimental setting in **Next.js** for disabling multithreading: ```js // Your Next.js config file (next.config.js) ... experimental: { workerThreads: false, cpus: 1 }, ... ``` This setting will force the build to be single-threaded, which limits the speed at which requests are made within the `getStaticProps`. As a result, the build runs slower but completes without errors. #### Throttling The following **Next.js** example uses `pThrottle`, and allows you to control the limit of API calls per interval. ```js import React from 'react'; import { allProducts } from '../../utils/getProducts'; import { gql } from '../../utils/hygraph-client'; import { throttledFetch } from '../../utils/throttle'; // Singular query used in getStaticProps const query = gql` query GetSingleItem($slug: String!) { product(where: { slug: $slug }) { name slug } } `; export async function getStaticPaths() { // One call to get all paths // No need to throttle this // Unless you have a LOT of these calls const products = await allProducts(); const paths = products.map((product) => ({ params: { slug: product?.slug }, })); return { paths, fallback: false }; } export async function getStaticProps({ params }) { // For each path, there will be an API call // We need to throttle this // We need to throttle it on a global throttle, so we need to set that externally // throttleFetch comes from a utility area and is shared among all dynamic route files /* import pThrottle from 'p-throttle' import hygraphClient from './hygraph-client' // Set the limit of # of calls per interval in ms (5 per second) const throttle = pThrottle({limit: 5, interval: 1000}) export const throttledFetch = throttle(async (...args) => { const [query, vars] = args const data = await hygraphClient.request(query, vars) return data }) */ const product = await throttledFetch(query, { slug: params.slug }); return { props: product, }; } export default function Page({ product }) { // Each page produced by paths and props return ( <>

{product.name}

); } ``` #### Exponential backoff Combining query execution with exponential backoff retries ensures applications remain reliable, even when encountering concurrent operation limits. 1. The `graphqlFetchWithRetry` function provides error handling and a retry strategy with exponential backoff, ensuring that errors such as concurrent operation limits or temporary rate limiting are retried effectively. ```js // lib/graphql.ts type GraphQLRequest = { endpoint: string; token?: string; query: string; variables?: Record; maxRetries?: number; baseDelayMs?: number; // starting delay (e.g., 250ms) maxDelayMs?: number; // cap delay (e.g., 5000ms) signal?: AbortSignal; }; type GraphQLResponse = { data?: T; errors?: Array<{ message: string; [key: string]: unknown }>; }; function sleep(ms: number, signal?: AbortSignal): Promise { return new Promise((resolve, reject) => { if (signal?.aborted) return reject(new DOMException('Aborted', 'AbortError')); const timer = setTimeout(resolve, ms); signal?.addEventListener('abort', () => { clearTimeout(timer); reject(new DOMException('Aborted', 'AbortError')); }); }); } // Exponential backoff with jitter and a hard minimum of 1000ms per retry. function computeBackoffMs( attempt: number, baseDelayMs: number, maxDelayMs: number, minimumMs = 1000 ): number { const exp = Math.max(minimumMs, baseDelayMs * Math.pow(2, attempt)); const jitter = Math.floor(Math.random() * Math.min(250, exp)); return Math.min(exp + jitter, maxDelayMs); } function containsConcurrencyError(errors?: Array<{ message: string }>): boolean { if (!errors) return false; return errors.some( (e) => typeof e.message === 'string' && e.message.toLowerCase().includes('concurrent operations limit exceeded') ); } export async function graphqlFetchWithRetry({ endpoint, token, query, variables, maxRetries = 5, baseDelayMs = 250, maxDelayMs = 5000, signal, }: GraphQLRequest): Promise { let lastError: unknown; for (let attempt = 0; attempt <= maxRetries; attempt++) { try { const res = await fetch(endpoint, { method: 'POST', headers: { 'content-type': 'application/json', ...(token ? { authorization: `Bearer ${token}` } : {}), }, body: JSON.stringify({ query, variables }), signal, cache: 'no-store', }); const is429 = res.status === 429; if (res.ok) { const json = (await res.json()) as GraphQLResponse; const concurrencyHit = containsConcurrencyError(json.errors); if (!concurrencyHit) { if (json.errors) { const messages = json.errors.map((e) => e.message).join('; '); throw new Error(`GraphQL error: ${messages}`); } return json.data as T; } // Concurrency limit via GraphQL errors – backoff and retry if (attempt < maxRetries) { const delay = computeBackoffMs(attempt, baseDelayMs, maxDelayMs, 1000); await sleep(delay, signal); continue; } throw new Error('Exceeded retries due to concurrent operations limit.'); } // HTTP 429 – backoff and retry (minimum 1s) if (is429 && attempt < maxRetries) { const delay = computeBackoffMs(attempt, baseDelayMs, maxDelayMs, 1000); await sleep(delay, signal); continue; } // Other non-OK statuses – surface const body = await res.text().catch(() => ''); throw new Error(`HTTP ${res.status}: ${body || res.statusText}`); } catch (err) { lastError = err; const isAbort = err instanceof DOMException && err.name === 'AbortError'; if (isAbort) throw err; if (attempt < maxRetries) { // Network or transient error – backoff with at least 1s const delay = computeBackoffMs(attempt, baseDelayMs, maxDelayMs, 1000); await sleep(delay, signal); continue; } break; } } throw lastError instanceof Error ? lastError : new Error('Request failed.'); } ``` 2. This example demonstrates how to execute a GraphQL query using the `graphqlFetchWithRetry` function defined in the previous step. ``` // app/api/products/route.ts import { NextResponse } from 'next/server'; import { graphqlFetchWithRetry } from '@/lib/graphql'; const GRAPHQL_ENDPOINT = process.env.GRAPHQL_ENDPOINT!; const GRAPHQL_TOKEN = process.env.GRAPHQL_TOKEN!; // Store server-side only export async function GET() { try { const query = ` query Products($first: Int!) { products(first: $first) { id name } } `; const data = await graphqlFetchWithRetry<{ products: Array<{ id: string; name: string }>; }>({ endpoint: GRAPHQL_ENDPOINT, token: GRAPHQL_TOKEN, query, variables: { first: 10 }, maxRetries: 5, baseDelayMs: 300, maxDelayMs: 5000, }); return NextResponse.json({ ok: true, data }); } catch (error) { return NextResponse.json( { ok: false, message: (error as Error).message }, { status: 429 } ); } } ``` ### Gatsby #### Concurrency override You can use `queryConcurrency` with our official Gatsby source plugin for Hygraph projects. This key indicates the number of promises ran at once when executing queries. Its default value is set to 10. This plugin has been deprecated and no longer receives support. However, if you are already using it and have encountered a problem with rate limits, then we suggest you use the `queryConcurrency` solution. #### Throttling The following **Gatsby** example uses `pThrottle`, and allows you to fetch 1 concurrent request maximum, with a minimum delay of 0.5 seconds. ```js import { createHttpLink } from "apollo-link-http"; import pThrottle from "p-throttle"; // Throttle fetches to max 1 concurrent request and // min. delay of 0.5 seconds. const throttledFetch = pThrottle( (...args) => { return fetch(...args); }, 1, 500); const link = createHttpLink({ uri: "/graphql" fetch: throttledFetch }); ``` ### Nuxt #### Thread limiting You can add the following to your `nuxt.config.js` to avoid getting a [429 error](/docs/api-reference/basics/errors#429-too-many-requests). It will stop GraphQL requests from overloading Hygraph's API limits when building. ```js // Your Nuxt config file (nuxt.config.js) generate: { concurrency: 250, //maximum number of requests per thread. This will only build 250 at a time based on the api rate limit interval: 200, //delay by 0.2s. You can adjust this to be higher if you still run into issues }, ``` --- # API Playground Source: https://hygraph.com/docs/api-reference/basics/api-playground The API playground is a great way to experiment with querying, and mutating data. You can find the API Playground from the sidebar once logged into your project. ![API Playground](/images/docs/api-reference/basics/playground.png) When you first open the API Playground, you will see some initial comments introducing GraphQL, followed by a query based on the model you have in your project. If the project has more than one model, the sample query will be provided using the first one. Under the demo query you will find some additional comments on keyboard shortcuts. The API Playground is also great for: - Testing authorization and access control with API keys. - Working with, and testing multiple environments. - Working with, and testing localized content. - Explore previously ran queries, and mutations. - Run previous queries in a single click - Explore queries, mutations, arguments, and more with explorer view. - Browse auto-generated schema documentation - Work with variables, and named queries Each tab of the API playground has four buttons: - **Prettify:** Cleans up the format and indentation of your typed query. - **Environment:** Allows you to select one of your configured environments, as well as the Management API. - **Stage:** Allows you to select one of your configured stages. - **Locale:** Allows you to select one of your configured locales. If you select `No Locale`, it will fetch all the content. By Default, the API Playground explorer located to the left of the screen will have the items of the demo query selected. You can modify the query manually, or use the checkboxes to do it instead. You can also add a new query by clicking on the **+** button next to the query tabs. You can find all your queries in the `My Queries` tab, located next to the `Explorer` tab. There, you can select which of your queries you wish to display, and edit the name by clicking on the pencil icon. At the bottom of the screen you will find two sections: Query variables, and HTTP headers. In the **Query variables** section you can provide the variables, if necessary for your query to work. If you're working with GraphQL APIs that require you to pass custom headers, you can do that in the **HTTP headers** section. --- # Authorization Source: https://hygraph.com/docs/api-reference/basics/authorization By default, queries and mutations require a [Permanent Auth Token](#permanent-auth-tokens), but you can configure the [Public API Permissions](#public-api-permissions) to allow unauthenticated requests. Hygraph allows you to configure access for each model, stage, environment, locale, and whether or not you permit mutations. ## Public API permissions By default, all new projects disable public API access. Enabling public access to your project means that anybody with your API endpoint can query sensitive data, and perform destructive actions if configured incorrectly. You can configure custom permissions, or initialize the defaults. For example, here you can see how to configure the Content API to allow access to READ the `Page` model on stages `Published` and `Draft` with the locale `English`. ![Create API permission](/images/docs/api-reference/basics/create-permission.png) You can also specify an optional condition that restricts access even further. For example, you could specify the condition `{ "id": "ckoimwlu80ck20a88z6i754cw" }`. This would then only allow you to fetch the `Page` with `id` `ckoimwlu80ck20a88z6i754cw`. [Learn more about Public API permissions](/docs/getting-started/access-and-permissions/content-api-permissions#set-up-public-api-access) ### Default public stage If no stage parameter is set on the GraphQL query, or via an [HTTP header](/docs/api-reference/content-api/headers), the content from the default set content stage will be served. ![Default Content Stage](/images/docs/api-reference/basics/default-content-stage.png) The default public stage is set to `PUBLISHED` for all new projects. ## Permanent auth tokens Permanent Auth Tokens are used for controlling access to querying, mutating content, and come in the form of Bearer token authentication. Similar to Public API permissions, you can create individual PATs for specific API actions. Some users create PATs for only querying data from the draft stage, for creating previews in staging environments. You may also opt to create a PAT for mutations, so you can perform changes to your content models server-side. If you are mutating data from the frontend, you should hide this token at the server level, and not expose your PAT client-side. The Permanent Auth Token must be passed via the `Authorization` header on HTTP requests in the format of a Bearer token: ``` Authorization: Bearer PERMANENT_AUTH_TOKEN ``` [Learn more about PAT permissions](/docs/getting-started/access-and-permissions/content-api-permissions#set-up-a-pat-with-model-specific-permissions) ## Third-party authentication With third-party app authentication support, you can enable Hygraph users to connect their Hygraph accounts to your application, and execute Content API and Management API requests. Permanent Auth Tokens are non-personal tokens. With PATs, you cannot attribute an API request to any specific user. With third-party authentication, you can control access within your applications based on roles and permissions configured in Hygraph, and then attribute any Hygraph mutation from your application to the user who initiated it. This is helpful in tracking content changes in visual builder applications, or activities in workflow integration apps, such as Jira, Slack, and so on. You can now link different activities performed by the same user, thus enhancing visibility and auditability. ### Authenticate Management API requests When you request tokens from Auth0 for Management API access, omit the `audience` parameter. Auth0 then returns both an `access_token` and an `id_token`. For Hygraph Management API requests, use the `id_token`, not the `access_token`. The `id_token` is associated with the authenticated user. The Management API uses Hygraph's [roles and permissions system](/docs/getting-started/access-and-permissions/user-roles-and-permissions), so requests made with that token are allowed or denied according to the permissions granted to that user within the project. To verify this, try querying the Management API with the `id_token`, including running a mutation. If the project has [audit logs](/docs/developer-guides/project/audit-logs) enabled, the corresponding entry should show that the action was performed by the authenticated user. ### Set up third-party connections 1. Before you set up third-party authentication, you need an OAuth `client ID` and `client secret` from Hygraph. Contact our Support team, and provide the following details of your application: - Name - Description - Logo - Type - Single page application or native application - Home page link - Allowed callback URLs (Allowed URLs for redirecting users after login) - Allowed logout URLs (Allowed URLs for redirecting users after logout) - Allowed Web Origins (List of URLs from where an authorization request can originate) After Hygraph approves your application, you receive an OAuth `client ID` and `client secret`. Store your client secret securely. If your client secret is compromised, contact Hygraph immediately to rotate to a new one and then update all authorized apps with the new client secret. 2. Update your app with OAuth credentials. ### Manage existing connections You can view a list of all active third-party connections in your account settings. At the top right of your screen, click your profile name, and then click **Account Settings**. Under **Connected apps**, you can view a list of all third-party apps and services connected to the account. To revoke access to any app, click **Revoke** next to it. ### Examples The following sections provide examples on setting up third-party authentication for single page and native applications. To learn how to set up authentication for other app types, see Auth0 documentation. #### Single page application 1. Clone the sample application locally. 2. Run `npm install` to install the necessary packages for the sample to run. 3. In the `public/js/app.js` file, add the `auth0Client` section. ```javascript const configureClient = async () => { const response = await fetchAuthConfig(); const config = await response.json(); auth0Client = await auth0.createAuth0Client({ domain: config.domain, clientId: config.clientId, // ++ add the auth0Client config auth0Client: { __useTenantInfo: config.isThirdPartyClient, }, }); }; ``` 4. To specify the application client ID and domain, make a copy of `auth_config.json.example` and rename it to `auth_config.json`. Open the `auth_config.json` file in a text editor and provide the correct values for your application: ```json { "domain": "auth.hygraph.com", "clientId": "CLIENT_ID", "isThirdPartyClient": true } ``` 5. Run `npm run dev` to start the application. #### Native application - Slack This example assumes that you've already built a Slack app in your workspace. If you haven't built a Slack app, follow the instructions here. 1. Define a new OAuth2 provider in the `manifest.ts` file. ```typescript // Define a new OAuth2 provider // Note: replace with your actual client ID const HygraphProvider = DefineOAuth2Provider({ provider_key: "hygraph", provider_type: Schema.providers.oauth2.CUSTOM, options: { client_id: "", scope: ["openid", "profile", "email", "offline_access"], provider_name: "Hygraph", authorization_url: "https://graphcms.auth0.com/authorize", token_url: "https://graphcms.auth0.com/oauth/token", token_url_config: { use_basic_auth_scheme: false, }, identity_config: { url: "https://graphcms.auth0.com/userinfo", account_identifier: "$.sub", http_method_type: "GET", }, authorization_url_extras: { prompt: "consent", access_type: "offline", audience: "https://graphcms.auth0.com/api/v2/", }, use_pkce: true, }, }); export default Manifest({ // ... // Add APIs that your application is going to call outgoingDomains: [ "eu-central-1.api.hygraph.com", "management.hygraph.com", "api-eu-central-1.hygraph.com", ], // ... // Tell your app about your OAuth2 providers here: externalAuthProviders: [HygraphProvider], }); ``` 2. Now, build a Slack function that creates a content entry on a user’s behalf. ```typescript import { DefineFunction, Schema, SlackFunction } from "deno-slack-sdk/mod.ts"; export const CreateContentFunctionDefinition = DefineFunction({ callback_id: "create_content_function", title: "Create content function", description: "A create content function", source_file: "functions/create_content_function.ts", input_parameters: { properties: { token: { type: Schema.slack.types.oauth2, oauth2_provider_key: "hygraph", }, project_id: { type: Schema.slack.types.string, description: "The project to create content for", }, model_id: { type: Schema.slack.types.string, description: "The model to create content for", }, channel_id: { type: Schema.slack.types.channel_id, description: "The channel to post the message in", } }, required: ["token", "project_id", "model_id", "channel_id"], }, output_parameters: { properties: { contentId: { type: Schema.types.string, description: "ID of the created content", }, }, required: ["contentId"], }, }); export default SlackFunction( CreateContentFunctionDefinition, async ({ inputs, client }) => { // Get the token: const tokenResponse = await client.apps.auth.external.get({ external_token_id: inputs.token, }); if (tokenResponse.error) { const error = `Failed to retrieve the external auth token due to ${tokenResponse.error}`; return { error }; } // If the token was retrieved successfully, use it: const externalToken = tokenResponse.external_token; // Make external API call with externalToken const response = await fetch( "https://eu-central-1.api.hygraph.com/graphql", { method: "POST", body: JSON.stringify({ query: `query Viewer { _viewer { ... on UserViewer { id user { id profile { email name } } project(id: "${inputs.project_id}") { environment(id: "master"){ authToken } } } } } `, }), headers: new Headers({ Authorization: `Bearer ${externalToken}`, "Content-Type": "application/json", }), } ); if (response.status != 200) { const body = await response.text(); const error = `Failed to call my endpoint! (status: ${response.status}, body: ${body})`; return { error }; } const responseBody = await response.json(); const uuid = crypto.randomUUID(); const contentApiToken = responseBody.data._viewer.project.environment.authToken; const createContentResponse = await fetch( "https://api-eu-central-1.hygraph.com/v2/cmaz8u024000b07w2o46szn1y/master", { method: "POST", body: JSON.stringify({ query: `mutation createContent($data: DemoModel23May20250843CreateInput!) { createDemoModel23May20250843(data: $data) { id } }`, variables: { data: { title: `Sample Content ${uuid}`, slug: `sampleContent${uuid}s`, // Add other fields as required by your model }, }, }), headers: new Headers({ Authorization: `Bearer ${contentApiToken}`, "Content-Type": "application/json", }), } ); if (createContentResponse.status != 200) { const body = await createContentResponse.text(); const error = `Failed to create content! (status: ${createContentResponse.status}, body: ${body})`; return { error }; } client.chat.postMessage({ channel: inputs.channel_id, text: `Content created successfully with ID: ${uuid}`, }); return { outputs: { contentId: uuid } }; } ); ``` 3. Next, create a Slack workflow. ```typescript import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts"; import { CreateContentFunction } from "../functions/content/definition.ts"; const CreateContentWorkflow = DefineWorkflow({ callback_id: "create_content_workflow", title: "Create Content", description: "A workflow to create new content", input_parameters: { properties: { interactivity: { type: Schema.slack.types.interactivity, }, }, required: ["interactivity"], }, }); CreateContentWorkflow.addStep(CreateContentFunction, { interactivity: CreateContentWorkflow.inputs.interactivity, token: { credential_source: "END_USER", }, }); export default CreateContentWorkflow; ``` 4. Create a trigger to invoke the custom function for the workflow. ```typescript import { Trigger } from "deno-slack-sdk/types.ts"; import { TriggerContextData, TriggerTypes } from "deno-slack-api/mod.ts"; import CreateContentWorkflow from "../workflows/create_content_workflow.ts"; const createContentTrigger: Trigger = { type: TriggerTypes.Shortcut, name: "Create Content trigger", description: "Trigger to create new content", workflow: `#/workflows/${CreateContentWorkflow.definition.callback_id}`, inputs: { interactivity: { value: TriggerContextData.Shortcut.interactivity, }, }, }; export default createContentTrigger; ``` 5. Add the client secret that you received from Hygraph for the Slack app. This secret is used to initiate the OAuth2 flow. ```bash slack external-auth add-secret --provider hygraph --secret "" ``` 6. Run the Slack application. ## API endpoints ![API Endpoints](/images/docs/api-reference/basics/api-endpoints.png) ### Regular read & write endpoint Each Hygraph project you create, or are invited to, has a unique GraphQL API endpoint (per environment). This endpoint permits you to both query and mutate data within your project. You will find it as **Content API** endpoint in your project's **API Access** settings. GraphQL introspection is enabled by default, so anybody with access to your endpoint can traverse the graph to see your content types, as well as all queries and mutations. This API endpoint also contains the current schema [environment](/docs/api-reference/basics/environments), by default this will be `master`. The API endpoint has the following composition: ``` https://[region].hygraph.com/v2/[projectId]/[environment] ``` Click [here](/docs/api-reference/basics/caching#regular-read-and-write-endpoint) to learn more about this endpoint. ### High performance endpoint This high performance endpoint is ideal for delivering content around the globe with low latency and high read-throughput. The API endpoint has the following composition: ``` https://[region].cdn.hygraph.com/content/[projectId]/[environment] ``` Click [here](/docs/api-reference/basics/caching#high-performance-endpoint) to learn more about this endpoint. ## Resources You might find the following documents useful: - [Permissions](/docs/getting-started/access-and-permissions/content-api-permissions): This document contains information on permissions, how they work, and their limits. - [Roles and permissions](/docs/getting-started/access-and-permissions/user-roles-and-permissions): This document contains information on how to work with roles and permissions in the Hygraph app. - [API access](/docs/getting-started/access-and-permissions/api-access): This document covers the API access section of the Hygraph app as well as its subsections: Endpoints, Content API, and Permanent auth tokens. --- # Caching Source: https://hygraph.com/docs/api-reference/basics/caching ## Overview All Content APIs are served via our globally distributed edge cache. Whenever a query is sent to the content API, its response is cached in multiple POP (data centers) around the globe. ![Hygraph CDN Map](/images/docs/api-reference/basics/cdn-map.svg) Hygraph handles all cache management for you! For faster queries, use `GET` requests, so browsers can leverage advanced caching abilities available in the headers. Hygraph comes with two different content API endpoints, served by 2 different CDN providers. To understand which endpoint you should use, look at the following table. | Endpoint name | Endpoint | Consistency | Access | | ----------------------------- | ------------------------------------------------------------------------- | -------------------------------------- | ------------ | | Regular read & write endpoint | `https://${region}.hygraph.com/v2/`
`${projectId}/${environment}` | Eventual (Read-after-write within POP) | Read & Write | | High performance endpoint | `https://${region}.cdn.hygraph.com/v2/`
`${projectId}/${environment}` | Eventual | Read & Write | CDN = Content-Delivery-Network You can see the endpoints in your project settings (Settings->API Access->Endpoints). ## Regular read & write endpoint Hygraph Studio does not display this legacy endpoint. The **Regular read & write endpoint** allows for reads and writes. All your requests are cached, and any mutation will invalidate the cache for your project. Even though this endpoint is eventually consistent, within a single region you will get read-after-write consistency. This endpoint can take up to 30s to update all caching nodes worldwide. This endpoint is ideal for delivering content around the globe with one endpoint, but it only has basic caching logic. ## High performance endpoint We use a state-of-the-art caching approach, and we have developed a high-performance edge service on the Fastly Compute Edge platform. The service is deployed close to your users around the world. You will benefit from continual improvements on the cache invalidation logic without any code changes on your part. This endpoint is ideal for delivering content around the globe with low latency and high read-throughput. This endpoint has **model + stage based invalidation**. This means that instead of invalidating the complete cache for content and schema changes, we only invalidate the models that were changed based on the mutations used. The rest will stay cached and, therefore, fast. Click [here](/docs/api-reference/basics/caching#model-stage-based-invalidation) to learn more. In some cases, entry-based cache invalidation is possible. [Click here to learn more](/docs/api-reference/basics/caching#entry-based-cache-invalidation) ## Consistency To understand both endpoints better, you should have a basic knowledge of cache consistency modes. You can be sure that any changes have persisted at Hygraph if your response was successful. ### Eventual consistency Some changes are not visible immediately after the update. For example, if you `create`, `delete` or `update` content the update is distributed around the globe with a short delay. Theoretically, if you read content right after a mutation, you may receive stale content. ### Read-after-write consistency In contrast, read-after-write consistency guarantees that a subsequent read after an update, delete, or create can see those changes immediately. This is only valid for operations hitting the same POP (point-of-presence). ## Model + stage based invalidation **Model + stage based invalidation**, which is only available for our [High performance endpoint](/docs/api-reference/basics/caching#smart-cache-invalidation), allows invalidating only the models that were changed based on the mutations used for content and schema changes, rather than invalidate the complete cache. Regarding queries that fetch related content that needs to be invalidated: We analyze query responses and invalidate only the cached queries that contain the changed model. For example: ```graphql { posts { title author { id } } } ``` ```graphql { "data": { "posts": [ { "title": "Post", "author": { "name": "Fabian" } } ] } } ``` Considering invalidation after a **schema change**, if a user were to update the `Author` model shown in the example above, this cached query would be invalidated, as it also returned the `Author` model. For **content changes**, we also take the stage into account, meaning that updating an `Author` entry, and not publishing it, would invalidate all cached queries that returned the `DRAFT` stage and the `Author` model. Queries that returned the `PUBLISHED` stage will remain cached. ## Smart cache invalidation Our System understands if mutations are flowing through the cache and invalidates the affected resources with an eventual consistency guarantee. ## Entry-based cache invalidation In some cases, cache invalidation can be done on an entry basis. - Caching of content of different stages remains independent. - Mutation of content in the `DRAFT` stage won't invalidate content in the `PUBLISHED` stage. There are three scenarios where entry-level tagging is applied and invalidation becomes more granular, targeting only responses embedding specific entries: - [Query of a single entry by `id`](/docs/api-reference/basics/caching#query-of-a-single-entry-by-id) - [Single reference fields](/docs/api-reference/basics/caching#single-reference-fields) - [Query of multiple models with a `where` argument involving only unique fields](/docs/api-reference/basics/caching#multi-model-query-with-unique-fields-only) (like a slug) We'll use the following example situation to explain the three possible cases: ![entry-based-caching](/images/docs/api-reference/basics/entry-based-caching.png) Let's consider the schema above. It represents a mock of a recipe website presenting image descriptions and preparation steps of the various dishes. Each recipe is also linked with a chef who created it. The GraphQL snippets in the following examples use the flag `[EBC]` to identify invalidation scenarios that work at entry level. ### Query of a single entry by `id` Following our recipe website example situation, in this scenario we query a single recipe (entry). When that recipe is found, the cached response for the query is only invalidated if there are mutations sent that affect this specific entry. The cached response will remain valid after mutations targeting other recipes, or any other model. ```graphql # Invalidated on # - changes to Recipe 'cltx865t89nu607uj4tvaact5' [EBC] query { recipe(where: { id: "cltx865t89nu607uj4tvaact5" }){ id title description } } ``` ### Single reference fields In this scenario and following our example situation, the query targets multiple recipes along with their associated chefs. Here, the cache mechanism becomes a little more complex as it has to deal with multiple relationships: ```graphql # Invalidated on: # - changes or creation of any Recipe # - changes of specific Chef related to the returned recipes [EBC] query { recipes(first: 5) { chef { #single reference field id name bio } } } ``` Changing any of the recipes or adding new recipes, will invalidate the cache associated with this query. This is because the query fetches the first five recipes, and any changes or additions could potentially alter this result. Additionally, any changes to the specific chefs who are directly associated with the returned recipes will also invalidate the cache. Other chefs' information will not affect the response and so it does not invalidate the cache. The EBC behavior from the previous snippet does not apply if chef parameters are part of the query `where` argument: ```graphql # Invalidated on: # - changes or creation of any Recipe # - changes or creation of any Chef query { recipes(where: { chef: { birthdate_gt: "1990-04-05T00:00:00Z" } }) { chef { id name bio } } } ``` Since the chef's `birthdate` is now a determining factor in the query results, any changes or additions to the chefs - regardless of their association with the returned recipes - can potentially alter the result of this query. For example, the birthday of another chef is updated and becomes included by the filter. Because of this, even modifying or adding chefs not directly associated with the returned recipes invalidates the cache. #### Nested references Entry-based cache invalidation also applies when the reference fields are nested deeply in the query tree, even if they are children of multi reference fields. ```graphql # Invalidated on: # - changes or creation of any Chef # - changes or creation of any Recipe # - changes of specific RecipeWalkthrough related to # the returned recipes [EBC] # - changes of specific Asset(video) related to # the returned RecipeWalkthrough [EBC] query { chefs { # multi model query name recipes { # multi reference field recipeWalkthrough { # single reference field video { # single reference field fileName url } } } } } ``` In this scenario, where the query involves multiple models and their relationships, the cache invalidation becomes a bit complex. Following our recipe website schema example: Any changes or additions to the chefs, recipes, or recipe walkthroughs, will invalidate the cache associated with this query. Additionally, any changes or additions to the specific assets (videos) related to the returned recipe walkthroughs will invalidate the cache as well. Modifying or adding assets not directly associated with the returned recipe walkthroughs does not affect the cache, so it will remain valid. ### Multi-model query with unique fields only This will only work for non-localized unique fields. In the following example, we are querying for a single recipe based on a unique field - the slug: ```graphql # Invalidated on: # - changes of specific `Recipe` with slug "classic-spaghetti-carbonara" query { recipe(where:{ slug: "classic-spaghetti-carbonara"}) { id slug title description createdAt } } ``` The cache will be invalidated only if there are changes to the specific recipe with the slug *"classic-spaghetti-carbonara"*. Any changes to other recipes will not affect the cache associated with this query. In this other example, the query fetches chefs and the recipes associated with each chef based on a unique field - the slug: ```graphql # Invalidated on: # - changes or creation of any Chef # - changes of specific `Recipe` with slug "classic-spaghetti-carbonara" query { chefs { id name recipes(where: {slug: "classic-spaghetti-carbonara"}) { id title description } } } ``` The cache will be invalidated if there are changes to the specific recipe with the slug "classic-spaghetti-carbonara" or any changes or additions to the chefs. Any changes to other recipes not having the slug "classic-spaghetti-carbonara" will not affect the cache associated with this query. ## stale-if-error In case of an outage of our APIs - this includes remote field origin errors as well- we will fall back for at least 24h to the latest cached content on our edge servers. This adds an additional reliability layer. This is only available on the High performance endpoint. The default `Stale-if-error` for all shared clusters is 86400s (1 day). You can use a header for the **High performance endpoint** that lets you set `stale-if-error` on a per query basis. ```graphql { "hyg-stale-if-error": "21600" } ``` The values are in seconds. ## stale-while-revalidate With the **High performance endpoint** you will get cached responses directly, while we update the content in the background. This means your content is always served on the edge, with low latency for your users. _Staleness_ refers to how long we deliver stale data while we revalidate the cache in the background if the cache was invalidated. This is only available on the High performance endpoint. The default `stale-while-revalidate` for our shared clusters is `0`. You can use a header for the **High performance endpoint** that lets you set `stale-while-revalidate` on a per query basis. ```graphql { "hyg-stale-while-revalidate": "27" } ``` The values are in seconds. ## Remote fields GraphQL queries that contain remote fields are cached differently. By default, a response is marked as cacheable when all remote field responses are cacheable according to rfc7234. You can control the TTL (Time-to-Live) cache by returning the `Cache-Control` response header. By default, we will set a TTL of `900s`, you can set a minimum TTL of `60s`. While it is also possible to respond with a `no-store` cache directive to disable the cache, this is not recommended, as it marks the entire response as not cacheable and will increase the load on your origin. --- # Content Freeze API reference Source: https://hygraph.com/docs/api-reference/basics/content-freeze A content freeze pauses content creation, editing, and publishing across an environment or an entire project, while schema changes are promoted. This reference covers the `contentFreezes` query and the `setContentFreeze` and `liftContentFreeze` mutations, so you can read, start, and lift a freeze directly through the Management API. For information on configuring a freeze from Studio and what editors see while one is active, see [Content Freeze](/docs/developer-guides/project/content-freeze). ## Authentication Use a Permanent Auth Token or App Token with the **Management API**. - **`contentFreezes`**: Any project member can read freeze history. - **`setContentFreeze`** and **`liftContentFreeze`**: Require the **Allows starting, scheduling, and lifting content freezes** permission. The Admin role includes it by default; you can also grant it on custom roles or PATs. ## Retrieve content freeze Returns every content freeze for the project, past and present, unless you filter the list. Run the `contentFreezes` query on `project`.
Parameters
| Argument | Type | Default | Description | |---|---|---|---| | `filter` | `ContentFreezeFilter` | `ALL` | Pass `OPEN` to return only freezes that haven't ended yet. | `contentFreezes` doesn't take pagination arguments. It returns the full list for the project on every call. It also doesn't filter by environment. If you need freezes for a specific environment, filter the returned list on the `environment` field.
Fields
| Field | Type | Description | |---|---|---| | `id` | `ID!` | Unique identifier for the freeze. | | `createdAt` | `DateTime!` | When the freeze record was created. | | `updatedAt` | `DateTime!` | When the freeze record last changed. | | `environment` | `Environment` | The environment this freeze applies to. Null for a project-wide freeze. | | `startAt` | `DateTime!` | When the freeze begins. | | `endAt` | `DateTime` | When the freeze ends. `null` for an open-ended freeze (no scheduled end). Set to a future time when you schedule an end, or to the current time when the freeze is lifted early. | | `message` | `String` | Announcement message shown to editors. Null if none was set. | | `setBy` | `CreatedBy` | Who started the freeze: a project member, Permanent Auth Token, or App Token. | | `liftedBy` | `CreatedBy` | Who ended the freeze early: a project member, Permanent Auth Token, or App Token. `null` if it reached its scheduled end. |
### Example query ```graphql query ContentFreezeQuery { viewer { project(id: "your-project-id") { contentFreezes(filter: OPEN) { id environment { name } startAt endAt message setBy { ... on Member { profile { name } } } liftedBy { ... on Member { id profile { name } } } } } } } ``` ```json { "data": { "viewer": { "project": { "contentFreezes": [ { "id": "fdacc0e66e044724bca5aca736ade52e", "environment": { "name": "master" }, "startAt": "2026-07-15T22:00:00.000Z", "endAt": "2026-07-22T22:00:00.000Z", "message": null, "setBy": { "profile": { "name": "Admin User" } }, "liftedBy": null } ] } } } } ``` ## Set content freeze Use the `setContentFreeze` mutation to start a freeze or reschedule the current freeze. Requires the **Allows starting, scheduling, and lifting content freezes** permission.
Parameters
| Argument | Type | Required | Description | |---|---|---|---| | `data.projectId` | `ID!` | Yes | The project to freeze. | | `data.environmentId` | `ID` | No | Target environment. Omit for a project-wide freeze. | | `data.startAt` | `DateTime` | No | When the freeze begins. Omit to start immediately. | | `data.endAt` | `DateTime` | No | When the freeze ends. Omit for an open-ended freeze. | | `data.message` | `String` | No | Announcement message shown to editors. | Each call applies to a single scope, either one environment or the whole project. To freeze multiple environments, call `setContentFreeze` once per environment ID. Calling it again for the same scope ends any open freeze for that scope and creates a new one. `startAt` and `endAt` must not be in the past. If you set `endAt`, it must not be before `startAt`.
Returns
`ContentFreezePayload`, containing `project`. Query `contentFreezes` to confirm the resulting freeze.
### Examples - **Set a project-wide freeze, starting immediately, with no end date:** ```graphql mutation SetProjectWideContentFreeze { setContentFreeze(data: { projectId: "your-project-id" }) { project { id } } } ``` ```json { "data": { "setContentFreeze": { "project": { "id": "your-project-id" } } } } ``` - **Set a freeze scoped to one environment, with a message:** ```graphql mutation SetEnvironmentContentFreeze { setContentFreeze( data: { projectId: "your-project-id" environmentId: "your-environment-id" message: "Freezing content while we promote schema changes." } ) { project { id } } } ``` ```json { "data": { "setContentFreeze": { "project": { "id": "your-project-id" } } } } ``` ## Lift content freeze Use the `liftContentFreeze` mutation to end the current freeze for a scope before its scheduled end. Requires the **Allows starting, scheduling, and lifting content freezes** permission.
Parameters
| Argument | Type | Required | Description | |---|---|---|---| | `data.projectId` | `ID!` | Yes | The project whose freeze you're lifting. | | `data.environmentId` | `ID` | No | Target environment. Omit to lift a project-wide freeze. | Lifting a freeze sets its `endAt` to the current time. The record isn't deleted; it remains queryable through `contentFreezes` as a record.
Returns
`ContentFreezePayload`, containing `project`. Query `contentFreezes` to confirm the freeze ended.
### Examples - **Lift a project-wide freeze:** ```graphql mutation LiftProjectWideContentFreeze { liftContentFreeze(data: { projectId: "your-project-id" }) { project { id } } } ``` ```json { "data": { "liftContentFreeze": { "project": { "id": "your-project-id" } } } } ``` - **Lift an environment-scoped freeze:** ```graphql mutation LiftEnvironmentContentFreeze { liftContentFreeze( data: { projectId: "your-project-id" environmentId: "your-environment-id" } ) { project { id } } } ``` ```json { "data": { "liftContentFreeze": { "project": { "id": "your-project-id" } } } } ``` ## Related docs - [Content Freeze](/docs/developer-guides/project/content-freeze): Learn what a content freeze does and how to configure it from Studio. - [Environments](/docs/api-reference/basics/environments): Create and manage the environments a freeze can target. - [Management API permissions](/docs/developer-guides/project/management-api-permissions): Confirm who holds the **Allows starting, scheduling, and lifting content freezes** permission. --- # Environments Source: https://hygraph.com/docs/api-reference/basics/environments ## Overview Safely work on isolated instances of your project with environments. Environments are for making changes to your GraphQL schema, and testing out new content types without breaking your production implementation, not to be used for editorial workflows. Depending on your current user role, and payment plan, you can manage your environments from your project settings. [Learn more about working with multiple environments](/docs/developer-guides/project/multiple-environments) ## Create environments You can create environments using our UI by following [these steps](/docs/developer-guides/project/manage-project-environments#create-an-environment). ## Switching environments You can switch environments using our UI by following [these steps](/docs/developer-guides/project/manage-project-environments#switch-environments). ## Environment endpoints Each environment has its own API endpoint, with the `alias` of the environment appended to the URL. API endpoints for both `master` and `dev` environments would follow the format of: ```bash https://[region].hygraph.com/v2/[projectId]/master ``` ```bash https://[region].hygraph.com/v2/[projectId]/dev ``` ## Promoting environments When you have made the necessary changes to a schema you want to promote to the master environment, you can do this by renaming the newly created environment to be `master`, overwriting the existing `master` environment and renaming it to something else. Since environments contain their own content, and webhooks, you will want to make sure you clone a new environment right before you apply the changes and promote, this ensures no loss of data. For projects with multiple users managing content at the same time you make changes to the schema inside an environment, you will probably want to use the Management SDK. For more information on how to use our UI to promote an environment to master, click [here](/docs/developer-guides/project/manage-project-environments#promote-environments-to-master). ## Delete an environment You can delete environments using our UI by following [these steps](/docs/developer-guides/project/manage-project-environments#delete-environments). --- # Errors Source: https://hygraph.com/docs/api-reference/basics/errors ## Overview Hygraph uses conventional HTTP responses codes when returning API requests. Codes in the 2xx range refer to a successful request, whereas responses in the 4xx range indicate there was an error due to the information you provided, such as invalid [query arguments](/docs/api-reference/content-api/queries#combining-arguments), or [auth](/docs/api-reference/basics/authorization) token. | Code | Description | | ----- | ---------------------------------------------------------------------------------------------------------- | | `200` | OK - Successful response | | `400` | [Bad request](#400-bad-request) - Typically due to invalid query arguments | | `401` | [Unauthorized](#401-unauthorized) - Usually no valid Permanent Auth Token was provided | | `402` | [Payment required](#402-payment-required) - Typically due to an expired trial or API/assetTraffic exceeded | | `403` | [Unauthorized request](#403-unauthorized-request) - The user needs more permissions | | `404` | Not Found - Typically invalid endpoint or environment requested | | `413` | Payload too large - Your request size exceeds the allowed limit | | `429` | [Too many requests](#429-too-many-requests) - You're making too many requests, slow down | | `500` | [Internal Server Error](#500-internal-server-error) - Something went wrong on our side | A typical GraphQL error will look like the below. The format of the error is typical for any `400` query or mutation request. ```graphql { "errors": [ { "message": "input:1: Field \"idd\" is not defined by type PostWhereInput. Did you mean id?\n" } ], "data": null, "extensions": { "requestId": "ckff8k1tc0nsg0150cssdkzxm" } } ``` It is clear from the error above that the field `idd` is not part of the Input Type. Hygraph will also return the `requestId` which can be used when requesting support. ## `400: Bad request` Hygraph will return a 400 if there is an error with the request. 400 errors are typically thrown when there is a client-side validation error, such as a missing required argument in your GraphQL query, or a malformed Permanent Auth Token. If your Permanent Auth Token is not a valid JWT, you will receive a 400 error. ## `401: Unauthorized` The endpoint you requested requires a valid [Permanent Auth Token](/docs/api-reference/basics/authorization), or a environment may not exist for this project. You might find the following documents useful for this: - [Authorization](/docs/api-reference/basics/authorization) - [Permanent auth tokens with specific models](/docs/getting-started/access-and-permissions/content-api-permissions#set-up-a-pat-with-model-specific-permissions) ## `402: Payment required` Exceeding the API operations and/or the asset traffic limit results in the API being blocked until the new billing period starts. Queries will return the following 402 error: > You have exceeded the limits of your project's subscription. To continue using your API, please enable billing under your project's settings to allow for overlimit billing. You may also get this error after a trial period has expired. ## `403: Unauthorized request` The user needs more permissions for either the Content API or the Management API. You might find the following documents useful for this: - [Roles and permissions](/docs/developer-guides/project/roles-and-permissions) - [Permissions](/docs/api-reference/basics/permissions) ## `413: Payload too large` To ensure delivery of optimal experiences to all customers, an API limit is enforced for request sizes on all GraphQL queries for the shared regions. The rate limiting depends on the current load of the shared region and the subscription plan. These limits can be lifted on dedicated clusters and enterprise plans. Please [contact sales](/contact) for further information. You'll get a `413` when you exceed the [limit for request size](/docs/api-reference/basics/api-limits#request-size), and you will get a response with the following payload: ```json { "errors": [ { "message": "Payload Too Large" } ], "data": null } ``` To resolve this issue, reduce the size of your GraphQL query or mutation. ## `429: Too many requests` To ensure delivery of optimal experiences to all customers, a rate limit is enforced on all GraphQL queries for the shared regions. The rate limiting depends on the current load of the shared region and the subscription plan. These limits can be lifted on dedicated clusters and enterprise plans. Please [contact sales](/contact) for further information. You'll get a `429` error when you exceed the rate limit. For exceeding the [number of requests per second limit](/docs/api-reference/basics/api-limits#requests-per-second), you will get a response with the following payload: ```json { "errors": [ { "message": "Too Many Requests" } ], "data": null } ``` To resolve this issue, retry the request after a brief delay. For exceeding the [concurrent requests limit](/docs/api-reference/basics/api-limits#concurrent-operations), you will get a response with the following payload: ```json { "errors": [ { "message": "Concurrent operations limit exceeded" } ], "data": null } ``` To resolve this issue, reduce the volume of your concurrent requests. Our document on [API rate limits](/docs/api-reference/basics/api-limits) shares information on limits according to subscription plan, as well as ways of handling API rate limits with [Next.js](/docs/api-reference/basics/api-limits#nextjs), [Gatsby](/docs/api-reference/basics/api-limits#gatsby), and [Nuxt](/docs/api-reference/basics/api-limits#nuxt). ## `500: Internal server error` `500s` errors could happen in multiple scenarios, whenever there's something wrong on our side. If you get a `500` error, make sure to take a look at our status page. --- # Query complexity Source: https://hygraph.com/docs/api-reference/basics/query-complexity ## Overview When working with GraphQL (GQL) queries, it's important to manage the complexity of your queries to ensure efficient and effective data retrieval. In the context of GQL, **query complexity** refers to the computational resources needed to fulfill a query. The complexity of a query increases with the number of fields and the depth of the query. - **Scalar fields**: Each scalar field in a query contributes one point to the query complexity. - **Relations / Unions**: Relations multiply their complexity times the level of nesting in the query. For example, if a query retrieves a list of posts and each post has multiple comments, the complexity of the query increases with each nested comment. This guide will help you with the following: - [How to split up your GQL queries to manage complexity](/docs/api-reference/basics/query-complexity#splitting-gql-queries) - [How to optimize union queries](/docs/api-reference/basics/query-complexity#union-queries) - [How to use the complexity tree JSON output to calculate the cost of your queries](/docs/api-reference/basics/query-complexity#complexity-tree-json-output) Some queries can be heavy on the database - especially due to heavy nesting or filtering - and in those cases, query splitting can help prevent them from failing. The complexity tree shown [here](/docs/api-reference/basics/query-complexity#complexity-tree-json-output) gives you information about the estimated cost of a query. If the JSON output shows values that are really high compared to the others, it might make sense to split the query. While query complexity matters, the reason a query fails can also be due to the number of entries, the concurrency of the query in a short period of time, or the general load of the infrastructure. If your query fails and the complexity tree does not show any significantly high values, please contact support so they can help you investigate the reason and find a solution. ## Splitting GQL queries To manage query complexity, you can split your GQL queries into smaller, more manageable parts: | Suggestion | Description | | ------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Limit the depth of your queries](/docs/api-reference/basics/query-complexity#example-1-limiting-query-depth) | Avoid deeply nested queries. Instead, break them up into multiple smaller queries. This can help reduce the complexity and make your queries more efficient. | | [Fetch only necessary fields](/docs/api-reference/basics/query-complexity#example-2-fetching-only-necessary-fields) | Minimize the number of fields you're retrieving in each query. Only fetch the fields that are necessary for your current operation. | | [Use pagination](/docs/api-reference/basics/query-complexity#example-3-using-pagination) | Hygraph supports various arguments for paginating content entries. By using these features, you can manage the amount of data retrieved in each query, thereby reducing the complexity. | Remember that the goal is to **reduce the complexity of your queries to ensure efficient and effective data retrieval**. By limiting the depth of your queries, fetching only necessary fields, and using pagination, you can manage the complexity of your GQL queries effectively. The following examples show you how you can split your GQL queries: ### Example 1: Limiting query depth Instead of a deeply nested query like this: ```graphql { posts { id comments { id author replies { id text user { id name } } } } } ``` You can split it into two separate queries: ```graphql { posts { id comments { id } } } ``` ```graphql { comments(id: "...") { id author replies { id text user { id name } } } } ``` ### Example 2: Fetching only necessary fields Intead of retrieving all fields, like this: ```graphql { post(where: { id: "..." }) { id title body author comments } } ``` You can retrieve only the necessary fields, like this: ```graphql { post(where: { id: "..." }) { id title } } ``` ### Example 3: Using pagination Hygraph supports various arguments for paginating content entries: - **`first`**: Seek forwards from the start of the result set. - **`last`**: Seek backwards from the end of the result set. - **`skip`**: Skip result set by a given amount. - **`before`**: Seek backwards before a specific ID. - **`after`**: Seeks forwards after a specific ID. You cannot combine `first` with `before`, or `last` with `after`. The default result size of results returned by queries fetching multiple entries is 10. You can provide a maximum of 100 to the **`first`**, or **`last`** arguments. - The limit of 10/100 applies to projects created after 14-06-2022. - Projects created before that date have a limit of 100/1000. - To learn more about this, check out our document on [Pagination](/docs/api-reference/content-api/pagination). You can use **`first`**, **`last`**, **`skip`**, **`before`**, and **`after`** arguments with any nested relations. In the following example, the `posts` model has `comments`: ```graphql { posts { id comments(first: 6, skip: 6) { id createdAt } } } ``` ## Union queries [Union types](/docs/api-reference/schema/field-types#union) allow to setup relational fields that point to different model types, while this feature allows for very flexible modelling of content, it can also open the door to queries that might not perform as well and could use some optimizations. Below we document means to optimize querying for content that is backed by a Union relation. Unions are typically queried like so: ```graphql { page(where: { id: "ckrks0ge0334m0b52onduq7r2" }) { id title blocks { __typename ... on Hero { title ctaLink } ... on Grid { title subtitle { markdown } } ... on Gallery { photos { url handle } } } } } ``` As schemas evolve and Union relations expand to many models, querying unions this way can become problematic. Particularly when every single possible type is queried with this format within the same query. ## Optimizing union queries We offer two ways of optimizing your union queries: - Enhanced Query Splitting with Entity Type **(Preferred solution)** - Optimizing union queries using Node ### Enhanced query splitting with Entity type This is the preferred solution, as it offers significantly better performance. Hygraph has introduced an improved query splitting feature using the `Entity` type and `entities` query entrypoint. This approach is particularly beneficial for handling complex union relationships and modular components. This new feature reflects Hygraph's commitment to providing advanced solutions for handling complex GraphQL queries with ease and efficiency. #### Implementation The `Entity` type provides a more streamlined approach compared to the traditional Node interface. It makes use of the typename to substantially increase performance. To do this, follow these two steps: **Step 1: Initial query using Entity type** This initial query fetches `id` and `__typename` for each block within a page, preparing for the detailed query in the next step. ```graphql query { page { id blocks { __typename ... on Entity { id } } } } ``` **Step 2: Detailed query for specific types** The second query specifically targets `Hero`, `Grid`, and `Gallery` entities based on the `id` and `__typename` obtained from the first query. Results are returned in the order of the `where` input. ```graphql query { entities(where: [{id: "ckrks0ge0334m0b52ienf67ag", typename: "Hero", stage: "DRAFT"}, {id: "ckrks0ge0334m0b52firha74a", typename: "Grid", stage: "DRAFT"}, {id: "ckrks0ge0334m0b52ifh2sd6a", typename: "Gallery", stage: "DRAFT"}]) { ... on Hero { id title } ... on Grid { id layout } ... on Gallery { id images } } } ``` Please note that `entities` is a top-level type. #### Benefits |Benefit |Description | |-------------------------|-------------------------------------------------------------------------| |Reduced Query Complexity |Simplifies queries by splitting them into manageable parts. | |Enhanced Performance |Improves efficiency by reducing the load in fetching complex data types. | |Flexible Data Fetching |Offers more control and precision in querying specific content types. | #### Example Use Case Consider a website with a dynamic layout consisting of `Hero`, `Grid`, and `Gallery` sections. **Enhanced query splitting with Entity type** would allow for efficient identification and retrieval of specific content types, ensuring high performance and flexibility in data handling. ### Optimizing union queries using Node In order to avoid performance impacts due to a large number of Union types in a relation, it is possible to change the way the content is queried so that it is done in a 2 step approach. Below we will be using the same query from the previous section as an example: **Step 1: Find out which documents are in fact connected** We will get the `__typename` and the `id` for all the connected documents in the union relation by using the Node interface like so: ```graphql { page(where: { id: "ckrks0ge0334m0b52onduq7r2" }) { id title blocks { __typename ... on Node { id } } } } ``` ```graphql { "data": { "page": { "id": "ckrks0ge0334m0b52onduq7r2", "title": "Sample Page 1", "blocks": [ { "id": "cks8t3o943h1l0d099v8xd072", "__typename": "Hero" }, { "id": "cksj3dxww0o2r0c57savzceub", "__typename": "Grid" }, { "id": "cksrocxds3mwa0a07rdtj7qvx", "__typename": "Grid" }, { "id": "cks8t36i83iq70b6035caxp6n", "__typename": "Gallery" } ] } } } ``` **Step 2: Query the connected types by `id`** With the retrieved information we can construct queries dynamically to fetch the affected documents. Considering the response we received from the previous query in **Step 1**, we will now go over the response and generate another query that will in fact get only the connected documents by `id`: ```graphql query heroBlocks { heros(where: { id_in: ["cks8t3o943h1l0d099v8xd072"] }) { title ctaLink } } query gridBlocks { grids( where: { id_in: ["cksj3dxww0o2r0c57savzceub", "cksrocxds3mwa0a07rdtj7qvx"] } ) { title subtitle { markdown } } } query galleryBlocks { galleries(where: { id_in: ["cks8t36i83iq70b6035caxp6n"] }) { photos { url handle } } } ``` Alternatively, you can combine these into a single query by using aliasing: ```graphql query blocks { heroBlocks: heros(where: { id_in: ["cks8t3o943h1l0d099v8xd072"] }) { title ctaLink } gridBlocks: grids( where: { id_in: ["cksj3dxww0o2r0c57savzceub", "cksrocxds3mwa0a07rdtj7qvx"] } ) { title subtitle { markdown } } galleryBlocks: galleries( where: { id_in: ["cks8t36i83iq70b6035caxp6n"] } ) { photos { url handle } } } ``` ## Complexity tree JSON output The complexity tree JSON output provides a detailed breakdown of the estimated and actual costs of your GraphQL query. This information can help you understand the computational resources required to fulfill your query and guide you in optimizing your queries for better performance. To get the complexity tree JSON for your query, you need to add the `"x-inspect-complexity": true` header to the playground. ### JSON Output Here is a brief explanation of the keys in the JSON output: - **`total_estimated_docs`:** The total number of documents estimated to be fetched by the query. - **`total_actual_docs`:** The total number of documents actually fetched by the query. - **`total_estimated_cost`:** The total estimated cost of the query. This includes the cost of fetching documents and any additional costs. - **`total_actual_cost`:** The total actual cost of the query. - **`complexityTree`:** A nested structure that breaks down the cost of each field in the query. Each node in the `complexityTree` has the following keys: - **`field_name`:** The name of the field in the query. - **`xpath`:** The path to the field in the query. - **`estimated_no_of_docs`:** The estimated number of documents fetched by this field. - **`additional_cost`:** Any additional cost associated with this field. - **`estimated_cost`:** The total estimated cost of this field (the sum of `estimated_no_of_docs` and `additional_cost`). - **`actual_no_of_docs`:** The actual number of documents fetched by this field. - **`actual_cost`:** The actual cost of this field. - **`children`:** Any nested fields within this field. Each child is also a node with the same structure. Nested objects multiply the estimated complexity by the pagination size default 10 (max 100). This is important to keep in mind when dealing with nested queries, as they can significantly increase the complexity of your query. ### JSON Output Example Consider the following query and its related complexity tree `JSON` output: ```graphql query Example { posts { title comments { text authors { name } } } } ``` ```json { "complexity": { "total_estimated_docs": 1110, "total_actual_docs": 0, "total_estimated_cost": 1116, "total_actual_cost": 0, "complexityTree": { "field_name": "Root", "estimated_no_of_docs": 0, "additional_cost": 0, "estimated_cost": 0, "actual_no_of_docs": 0, "actual_cost": 0, "children": [ { "field_name": "posts", "xpath": "posts.#", "estimated_no_of_docs": 10, "additional_cost": 2, "estimated_cost": 12, "actual_no_of_docs": 0, "actual_cost": 0, "children": [ { "field_name": "title", "estimated_no_of_docs": 0, "additional_cost": 0, "estimated_cost": 0, "actual_no_of_docs": 0, "actual_cost": 0 }, { "field_name": "comments", "xpath": "posts.#.comments.#", "estimated_no_of_docs": 100, "additional_cost": 2, "estimated_cost": 102, "actual_no_of_docs": 0, "actual_cost": 0, "children": [ { "field_name": "text", "estimated_no_of_docs ": 0, "additional_cost": 0, "estimated_cost": 0, "actual_no_of_docs": 0, "actual_cost": 0 }, { "field_name": "authors", "xpath": "posts.#.comments.#.authors.#", "estimated_no_of_docs": 1000, "additional_cost": 2, "estimated_cost": 1002, "actual_no_of_docs": 0, "actual_cost": 0, "children": [ { "field_name": "name", "estimated_no_of_docs": 0, "additional_cost": 0, "estimated_cost": 0, "actual_no_of_docs": 0, "actual_cost": 0 } ] } ] } ] } ] } } } ``` This JSON output shows us that the total estimated cost of the query is 1116, which includes fetching 1110 documents and additional costs. However, since the query did not return any content for this example(there was no real content in the project), the actual costs and documents fetched are 0. Despite this, the query is still costly due to the nested structure, hence the high estimated cost. The `complexityTree` provides a breakdown of the costs for each field in the query. For example, the `posts` field is estimated to fetch 10 documents with an additional cost of 2, resulting in an estimated cost of 12. Within the `posts` field, the `comments` field is estimated to fetch 100 documents with an additional cost of 2, resulting in an estimated cost of 102. The `authors` field within `comments` is estimated to fetch 1000 documents with an additional cost of 2, resulting in an estimated cost of 1002. This is because of the multiplication of nested fields that we mentioned before. By examining the `complexityTree`, you can identify which fields contribute the most to the complexity of your query and optimize accordingly. Keep in mind that nested objects multiply the estimated complexity by the pagination size, so be mindful of this when structuring your queries. --- # Troubleshooting Source: https://hygraph.com/docs/api-reference/basics/troubleshooting ## Can't access part of the functionality This might be a [permissions](/docs/api-reference/basics/permissions) issue. Take into consideration that while Management API permissions are global, content permissions are environment specific. If you're getting a [401 error](/docs/api-reference/basics/errors#401-unauthorized), the endpoint you requested requires a valid [Permanent Auth Token](/docs/api-reference/basics/authorization#permanent-auth-tokens), or a environment may not exist for this project. ## Queries not returning complete results This is a [pagination](/docs/api-reference/content-api/pagination) issue. The default size of results returned by queries fetching multiple entries is 10. You can provide a maximum of 100 to the first, or last arguments. The limit of 10/100 applies to projects created after 14-06-2022. Projects created before that date have a limit of 100/1000. Our pagination document also contains information about [pagination limits](/docs/api-reference/content-api/pagination#pagination-limits), [nested pagination](/docs/api-reference/content-api/pagination#nested-pagination), and [relay cursor connections](/docs/api-reference/content-api/pagination#relay-cursor-connections). ## My query is too slow This could be a [query complexity issue](/docs/api-reference/basics/query-complexity). Our document on [splitting GraphQL queries](/docs/api-reference/basics/query-complexity#splitting-gql-queries) in order to reduce their complexity, suggests three possible solutions: - [Limiting query depth](/docs/api-reference/basics/query-complexity#example-1-limiting-query-depth) - [Fetching only necessary fields](/docs/api-reference/basics/query-complexity#example-2-fetching-only-necessary-fields) - [Using pagination](/docs/api-reference/basics/query-complexity#example-3-using-pagination) The document also contains information on [union query optimization](/docs/api-reference/basics/query-complexity#union-queries), and on [how to use the complexity tree JSON output to calculate the cost of your queries](/docs/api-reference/basics/query-complexity#complexity-tree-json-output). ## No invite found with this code If when accepting an invitation to a project you get the error message `No invite found with this code`, ensure you are logged in with the right account. ## Too many requests This is related to our [API limits](/docs/api-reference/basics/api-limits). When you exceed them, you get a [429 error](/docs/api-reference/basics/errors#429-too-many-requests). Our API limits document contains information on how to tackle this using [Next.js](/docs/api-reference/basics/api-limits#nextjs), [Gatsby](/docs/api-reference/basics/api-limits#gatsby), and [Nuxt](/docs/api-reference/basics/api-limits#nuxt). The API limits depend on the current load of the shared region and the subscription plan. These limits can be lifted on dedicated clusters and enterprise plans. You can [contact sales](/contact) to request a custom plan. ## (Free plans only) Asset traffic limit reached As soon as a project on a **free plan** exceeds its asset traffic limit, all asset URLs return the following error: “You have exceeded the asset traffic limits of your project's subscription. To continue serving assets, please enable billing under your project’s settings to allow for overlimit billing.” You can either: - Wait until the limit resets to 0 at the start of the next billing cycle. Once the new cycle begins, you can start querying your assets. - Upgrade to a paid plan, which immediately resets the limit to 0. In this case, reach out to Hygraph Support so we can verify that the project has been unblocked. --- # Usage metrics API reference Source: https://hygraph.com/docs/api-reference/basics/usage-metrics The Hygraph Management API gives you programmatic access to your project's usage and safeguard metrics. You can use this data to build external dashboards or set up alerting for your own monitoring stack. This data is also available in the [Usage dashboard](/docs/developer-guides/project/usage-dashboard). ## Prerequisites To query usage metrics, your Permanent Auth Token must include the **Read observability data** Management API permission. For more information, see [Management API permissions](/docs/getting-started/access-and-permissions/management-api-permissions#pats-experimental-ai-usage). ## Query structure On the Management API, the `usage` field exists on: - `viewer.project`: Project-level metrics. - `viewer.project.environment`: Environment-level metrics. ```graphql viewer { project(id: $projectId) { usage(end: $end, start: $start) { # project-level metrics } environment(id: $environmentId) { usage(end: $end, start: $start) { # environment-level metrics } } } } ``` Replace `$projectId` and `$environmentId` with the IDs from your project settings. You can find the Project ID and environment ID in the project URL: `https://app.hygraph.com///`. ### Arguments | Argument | Type | Description | |---|---|---| | `end` | `DateTime!` | End of the requested date range. | | `start` | `DateTime!` | Start of the requested date range. The API only returns data within your plan’s **retention window**. | Retention depends on your plan: - Hobby plans: 30 days - Paid plans: 365 days Requesting a `start` date earlier than your retention window does not return an error. The API clips the start to the earliest retained date and returns data from that point forward. ## Metrics ### Billing metrics Billing metrics are summed across all environments at project level. These metrics reflect consumption against your plan limits. | Field | Units | Description | |---|---|---| | `apiOperations` | Request count per day | External Content API requests, excluding Studio traffic. Aggregated for usage reporting. | | `assetTraffic` | Bytes per day | Asset data transferred, counted toward asset bandwidth usage. | ### Technical safeguard metrics Technical safeguard metrics relate to the same limits as [API limits](/docs/api-reference/basics/api-limits). They exist on both project and environment usage. For interpreting these limits, **environment** usage is usually the best match, because limits apply **per environment**. **At project level** (`viewer.project { usage }`): - `maxRps*`, `maxConcurrency*`, `maxRequestSize*`: For each day in the range, the maximum value observed across all environments. This is the largest per-environment value for that day, not a single merged peak across the whole project. - `*OverLimit*`: For each day in the range, the total count across all environments. **At environment level** (`viewer.project.environment { usage }`): - `maxRps*`, `maxConcurrency*`, `maxRequestSize*`: For each day in the range, the maximum value observed for that environment only. - `*OverLimit*`: For each day in the range, the total count for that environment only. | Field | Description | |---|---| | `maxRpsQueries` | Peak requests per second for queries. | | `maxRpsMutations` | Peak requests per second for mutations. | | `maxConcurrencyQueries` | Peak in-flight concurrent operations for queries. | | `maxConcurrencyMutations` | Peak in-flight concurrent operations for mutations. | | `maxRequestSizeQueries` | Largest GraphQL payload size observed for queries. | | `maxRequestSizeMutations` | Largest GraphQL payload size observed for mutations. | | `rpsOverLimitQueries` | Requests that exceeded the RPS limit for queries. | | `rpsOverLimitMutations` | Requests that exceeded the RPS limit for mutations. | | `concurrencyOverLimitQueries` | Requests that exceeded the concurrency limit for queries. | | `concurrencyOverLimitMutations` | Requests that exceeded the concurrency limit for mutations. | | `requestSizeOverLimitQueries` | Requests that exceeded the request size limit for queries. | | `requestSizeOverLimitMutations` | Requests that exceeded the request size limit for mutations. | ## Response | Field | Type | Description | |---|---|---| | `timestamps` | `[DateTime!]!` | Start of each day in the date range. | | `values` | `[Float!]!` | One value per day in the date range, in the same order. | Metrics update once per day and responses are cached for a short interval. Avoid polling more frequently than every few minutes. Data for the current day may be incomplete; use the previous day's data for reliable trend analysis. ## Example query This query returns project-level billing metrics and environment-level safeguard metrics over a custom date range. ```graphql query UsageAndSafeguards { viewer { project(id: "Your_project_ID") { usage(end: "2026-05-08T00:00:00.000Z", start: "2026-05-06T00:00:00.000Z") { apiOperations { timestamps values } assetTraffic { timestamps values } } environment(id: "Your_environment_ID") { usage(end: "2026-05-08T00:00:00.000Z", start: "2026-05-06T00:00:00.000Z") { maxRpsQueries { timestamps values } rpsOverLimitQueries { timestamps values } maxConcurrencyMutations { timestamps values } concurrencyOverLimitMutations { timestamps values } maxRequestSizeMutations { timestamps values } requestSizeOverLimitMutations { timestamps values } } } } } } ``` ```json { "data": { "viewer": { "project": { "usage": { "apiOperations": { "timestamps": [ "2026-05-06T00:00:00.000Z", "2026-05-07T00:00:00.000Z", "2026-05-08T00:00:00.000Z" ], "values": [164556, 156144, 202296] }, "assetTraffic": { "timestamps": [ "2026-05-06T00:00:00.000Z", "2026-05-07T00:00:00.000Z", "2026-05-08T00:00:00.000Z" ], "values": [4819734309, 9047785649, 6026574533] } }, "environment": { "usage": { "maxRpsQueries": { "timestamps": [ "2026-05-06T00:00:00.000Z", "2026-05-07T00:00:00.000Z", "2026-05-08T00:00:00.000Z" ], "values": [47, 31, 50] }, "rpsOverLimitQueries": { "timestamps": [ "2026-05-06T00:00:00.000Z", "2026-05-07T00:00:00.000Z", "2026-05-08T00:00:00.000Z" ], "values": [0, 0, 99] }, "maxConcurrencyMutations": { "timestamps": [ "2026-05-06T00:00:00.000Z", "2026-05-07T00:00:00.000Z", "2026-05-08T00:00:00.000Z" ], "values": [1, 1, 1] }, "concurrencyOverLimitMutations": { "timestamps": [ "2026-05-06T00:00:00.000Z", "2026-05-07T00:00:00.000Z", "2026-05-08T00:00:00.000Z" ], "values": [0, 0, 0] }, "maxRequestSizeMutations": { "timestamps": [ "2026-05-06T00:00:00.000Z", "2026-05-07T00:00:00.000Z", "2026-05-08T00:00:00.000Z" ], "values": [36309, 12539, 0] }, "requestSizeOverLimitMutations": { "timestamps": [ "2026-05-06T00:00:00.000Z", "2026-05-07T00:00:00.000Z", "2026-05-08T00:00:00.000Z" ], "values": [0, 0, 0] } } } } } } } ``` --- # Webhooks Source: https://hygraph.com/docs/api-reference/basics/webhooks Hygraph webhooks are a fundamental method for observing changes that happen to content within your project. Whether new content is published, or existing is updated, subscribe to these events, and get notified via a `POST`, `DELETE`, `PUT`, or `GET` request to perform your own custom business logic. For example, you could use webhooks for: - Syncing data to external search engine, - Syncing data with external PIM, - Redeploying your static website when content or assets change, - Triggering email campaigns based on new content added. - Push messages into Slack. **Webhooks are environment specific**. This means their configuration is applied per environment. Take this into consideration if you're working with a project using more than one environment. ## Triggers Triggers can be configured to listen to one or more content model, stage, and action events. It is also possible to specify a trigger source, which allows triggering only when content is changed by the specified source: a [Permanent Auth Token (PAT)](/docs/api-reference/basics/authorization#permanent-auth-tokens), a project member editing content via the webapp or via public API. | Trigger | | | ------------- | ------------------------------------------------------------------------------------------------------------------ | | Content Model | All models, including `Asset` | | Stage | Draft, Published, and [custom content stages](/docs/api-reference/content-api/content-stages) | | Action | Create, Update, Delete, Publish, Unpublish, and Transition Step | | Source | [Permanent Auth Token](/docs/api-reference/basics/authorization#permanent-auth-tokens), Project Member, Public API | Ensure that your system is set up to respond to webhook requests as quickly as possible. As soon as Hygraph receives an HTTP 2xx status code, we process the next webhook request. This prevents any delays or failure in webhook delivery. Note the following timeouts: - Webhook requests time out after 3000 ms (3 seconds). - The current retry policy is 4 retries on top of the initial request, that is, 5 requests in total. We recommend that you should not set up slow operations, such as builds within the webhook. ## Receiving a webhook Once events occur, a new webhook will be queued. It could be several minutes before a webhook is triggered, so make sure to create your application with this in mind. Once an event occurs, data is sent as JSON in a `POST`, `DELETE`, `PUT`, or `GET` request body to the configured URL. The contents of the request always contain the `operation`, and `data` of the event. The `data` object will always contain the `__typename`, `id` and `stage` keys. If the webhook is configured to include entry payloads, the following keys will also be included inside the `data` object: - All other non-localized fields on the entry. - The `id` and `__typename` of any related entries. - An optional `localizations` array, containing any localized fields on the entry. The snapshot of the current stage is only sent in the webhook body. This means that: - Create, Update, and Delete actions only send the draft version. - Publishing and Unpublishing actions only send the published version. ## Securing webhooks It is best to protect the endpoints which your webhooks invoke. This prevents unauthorized actions on your server, and better protects your users from any suspicious activity. You should set a shared `secret key` on your webhook, which can be used to validate the request came from Hygraph. ![Hygraph webhook secret key](/images/docs/api-reference/basics/webhook-secret-key.png) Hygraph will send the header `gcms-signature` on webhooks with a shared secret key set. A `gcms-signature` header value looks like this: ``` sign=x0jU8z7AXAARIDBgsiVyfOG000wb2HhqN/mxl6+RSMk=, env=test, t=1631270481036 ``` ## Webhooks & assets When using webhooks with the Hygraph Asset Management System, remember that asset entry creation and asset uploads are asynchronous processes. The asset entry is created before the upload finishes, so "create" webhooks are triggered before the upload completes. This can cause external systems to fail if they expect the asset to be available when they receive the "create" webhook. When the asset entry is created, its status is `ASSET_CREATE_PENDING`. It only becomes accessible via its URL once the status changes to `ASSET_UPLOAD_COMPLETE`. The webhook system sends an `UPDATE` action for the asset model when the upload status updates to `ASSET_UPLOAD_COMPLETE`. For example: ```graphql { "operation": "create", "data": { "__typename": "Asset", <-- typical webhook payload --> "localizations": [ { <-- asset fields --> "upload": { "error": null, "expiresAt": "2025-03-19T17:14:34+00:00", "status": "ASSET_CREATE_PENDING" }, } ], } } ``` ```graphql { "operation": "update", "data": { "__typename": "Asset", "id": "cm8g1gg9n003c07upws5mpn9r", "localizations": [ { "fileName": "gold_logo.png", "handle": "cm8g1gg9n003d07upsgkl6rwh", "height": 1000, "locale": "en", "mimeType": "image/png", "size": 196374, "upload": { "status": "ASSET_UPLOAD_COMPLETE" }, "width": 1000 } ], "stage": "DRAFT" } } ``` Check the `data.localizations[0].upload.status` information. It will be being pending on create, and when it's complete it will show an update webhook. ### Webhooks status for assets | STATUS | DESCRIPTION | | ----------------------- | --------------------------------------------------------------------------------------------------- | | `ASSET_CREATE_PENDING` | A new asset entry has been created but the asset has not yet been uploaded. | | `ASSET_UPLOAD_COMPLETE` | The asset upload is complete. | | `ASSET_UPDATE_PENDING` | An existing asset entry has started the update process but the new asset has not yet been uploaded. | | `ASSET_ERROR_UPLOAD` | An error has occurred during asset creation & upload process. | ## Validating webhook signatures Using the `gcms-signature` header, you can validate the request came from Hygraph. You'll need to generate a HMAC with the SHA256 hash function to generate a signature you can use to compare against the `gcms-signature` header. ### Hygraph utils To make things easier for developers working with Node, we've released a small utility that will construct a new signature for you, with your values. Be sure to use the original, unmodified request body when verifying webhook signatures. Avoid parsing the request first, for example with `req.json()`, as this can change the payload and cause signature verification to fail. ```bash npm install @hygraph/utils ``` ```js const { verifyWebhookSignature } = require('@hygraph/utils'); const secret = 'rCNwyiloY3oJYYkxgpBXaleIiUv5MYlx'; const body = '...'; // Raw request text, that is req.text() const signature = '...'; // Typically req.headers['gcms-signature'] const isValid = verifyWebhookSignature({ body, signature, secret }); ``` You'll need the request body and headers to pass to `verifyWebhookSignature`. If `isValid` is truthy, then you can safely execute your webhook handler code knowing the request is genuine, otherwise you should abort any further action. ### Manual verification You may also verify webhook signatures manually by generating your own signature using whatever cryptographic library can generate a SHA256 digest. Let's break the `gcms-signature` header down: ``` sign=x0jU8z7AXAARIDBgsiVyfOG000wb2HhqN/mxl6+RSMk=, env=master, t=1631270481036 ``` - `sign=` is the signature - `env=` is the environment of the Hygraph project - `t=` is the timestamp of the event #### Step 1: Extract the signature and timestamp from the header First you'll need to get the signature, and timestamp from the header so they can be used to construct a new payload. If you're using JavaScript, it could look something like this: ```jsx const [rawSign, rawEnv, rawTimestamp] = signature.split(', '); const sign = rawSign.replace('sign=', ''); const EnvironmentName = rawEnv.replace('env=', ''); const Timestamp = parseInt(rawTimestamp.replace('t=', '')); ``` #### Step 2: Prepare the payload string Use req.text() to read the raw request body. Webhook signatures must be verified against the exact payload sent by Hygraph. Re-serializing a parsed object can change the payload and cause verification to fail. You'll next need to create a string of the payload that will be hashed, using the request body. If you're using JavaScript, it could look something like: ``` let payload = JSON.stringify({ Body: req.text(), EnvironmentName, TimeStamp: Timestamp, }); ``` #### Step 3: Generate the signature Next, you'll want to generate the digest for comparison. If you're using JavaScript, it could look something like: ```jsx const { createHmac } = require('crypto'); const hash = createHmac('sha256', secret).update(payload).digest('base64'); ``` #### Step 4: Compare the signatures match! All that's left to do is compare that the `sign=` value and `hash` match. If you're using JavaScript, this may look something like: ```jsx const isValid = sign === hash; ``` ## Localizations with webhooks If the webhook is configured to include entry payloads, all localized fields on the entry will be included in the webhook body under the `localizations` key. See the section below for [some examples](#example-payload). Webhook events are scoped to the entry and are **not** locale specific. For example, if publishing changes to an `en` entry localization, the webhook will still include any other localized versions of that entry in the payload. ## Example payload Below are examples of an event on `DRAFT`, `PUBLISHED` and a custom QA stage, which include the entry payload. There is also an example without the entry payload included. Additionally, we also have an example of an event for a workflow transition step, with and without the entry payload. In the examples below we have a `Post` model. ```json { "operation": "create", "data": { "__typename": "Post", "content": "Start using webhooks today, for free.", "createdAt": "2020-09-16T13:11:57.345138+00:00", "description": "This is a description", "id": "ckf5emloo021s0161iqos7enp", "image": { "__typename": "Asset", "id": "ckf5em5hc02100157x5n00lwb" }, "localizations": [ { "locale": "en", "title": "Hygraph Webhook Payload Changes" } ], "publishedAt": null, "stage": "DRAFT", "updatedAt": "2020-09-16T13:11:57.345138+00:00" } } ``` ```json { "operation": "publish", "data": { "__typename": "Post", "content": "Start using webhooks today, for free.", "createdAt": "2020-09-16T13:11:57.345138+00:00", "description": "This is a description", "id": "ckf5emloo021s0161iqos7enp", "image": { "__typename": "Asset", "id": "ckf5em5hc02100157x5n00lwb" }, "localizations": [ { "locale": "en", "title": "Hygraph Webhook Payload Changes!" } ], "publishedAt": "2020-09-16T13:14:12.782833+00:00", "stage": "PUBLISHED", "updatedAt": "2020-09-16T13:13:50.424325+00:00" } } ``` ```json { "operation": "publish", "data": { "__typename": "Post", "content": "Start using webhooks today, for free.", "createdAt": "2020-09-16T13:11:57.345138+00:00", "description": "This is a description", "id": "ckf5emloo021s0161iqos7enp", "image": { "__typename": "Asset", "id": "ckf5em5hc02100157x5n00lwb" }, "localizations": [ { "locale": "en", "title": "Hygraph Webhook Payload Changes!" } ], "publishedAt": "2020-09-16T13:14:12.782833+00:00", "stage": "QA", "updatedAt": "2020-09-16T13:13:50.424325+00:00" } } ``` ```json { "operation": "publish", "data": { "__typename": "Post", "id": "ckf5emloo021s0161iqos7enp", "stage": "PUBLISHED" } } ``` ```json { "operation": "transition_step", "data": { "__typename": "DemoModel", "content": { "__typename": "DemoModelContentRichText", "json": { "children": [ { "children": [ { "text": "This is the first entry." } ], "type": "paragraph" } ] }, "references": [] }, "createdAt": "2025-06-25T09:36:19.662343+00:00", "createdBy": { "__typename": "User", "id": "cmab5qcb900l907uq74xz3lqa" }, "id": "cmcbrf372oqcb08uvr42y0api", "image": [ { "__typename": "Asset", "id": "cmcaodlnt9xl007vxn6u1g12u" } ], "multimediaContent": null, "publishedAt": null, "publishedBy": null, "scheduledIn": [], "slug": "entry-12", "stage": "DRAFT", "subtitle": null, "title": "Entry 12", "updatedAt": "2025-06-26T11:52:32.853446+00:00", "updatedBy": { "__typename": "User", "id": "cmab5qcb900l907uq74xz3lqa" }, "workflowStep": "reworkChanges" } } ``` ```json { "operation":"transition_step", "data":{ "__typename":"DemoModel", "id":"cmcbrf372oqcb08uvr42y0api", "stage":"DRAFT" } } ``` ## Create a webhook 1. In your project, navigate to **Project Settings > Automation > Webhooks**. 2. Click `Add webhook`. 3. Fill out the configuration for your webhook. 4. Click `Add webhook` to save. ### Configuration - A name for organizing and finding your webhooks - A short description to inform others what your webhook does - Toggle if you want to include the payload or not - The URL to be called - The content model(s) that will be watched - The content stage to be watched - The action to be watched - Any additional headers to pass along ## Enable / Disable webhooks 1. In your project, navigate to **Project Settings > Automation > Webhooks**. 2. Click the context menu next to the webhook you want to edit. 3. Click **Pause** to disable the webhook. - To enable the webhook, click the context menu and then click **Resume**. ## Delete a webhook 1. In your project, navigate to **Project Settings > Automation > Webhooks**. 2. Click the context menu next to the webhook you want to delete, and then click **Delete**. 3. Confirm you want to delete the webhook. ## Webhook logs Hygraph stores logs for all your webhooks, which can help you debugging webhook calls and checking the returned status codes and responses from your endpoints. Follow these steps to view your webhook logs: 1. In your project, navigate to **Project Settings > Automation > Webhooks**. 2. Click **View logs** next to the webhook for which you want to check logs. ![Hygraph webhook logs button](/images/docs/api-reference/basics/webhook-logs-button.png) You will see a list of webhooks being sent to your specified endpoint. The logs include a timestamp, HTTP status code, the content model, the performed action and the duration of the request. ![Hygraph webhook logs overview](/images/docs/api-reference/basics/webhook-log-overview.png) Clicking on one of the items opens up a detail view, showing you both the request and response payload and the information from the overview. The request and response body is currently truncated at 500kb and 200kb respectively. ![Hygraph webhook logs details](/images/docs/api-reference/basics/webhooks-log-details.png) Webhook Logs are currently stored with a retention of 7 days. --- # Content stages in Hygraph Source: https://hygraph.com/docs/api-reference/content-api/content-stages ## Overview You can create your own content stages inside the Hygraph UI, and query content from these stages, as well as publish to. By default all content is served from the `DRAFT` stage, unless configured otherwise via a Permanent Auth Token, or by filtering. You can [create custom content stages](/docs/developer-guides/content/content-stages#custom-content-stages) to publish to from `DRAFT`. Each project comes with stages `DRAFT`, and `PUBLISHED`. Custom content stages are available to paid plans. Upgrade your plan. **Content stages are environment specific**. This means their configuration is applied per environment. Take this into consideration if you're working with a project using more than one environment. ## Create a content stage 1. Navigate to your project settings. 2. Open the Content Stages tab. 3. Press "Add Stage" at the bottom of the list of stages. 4. Provide the Display name, API ID, a Color for the label and Description in the modal. 5. Click `Create`. Creating content stages besides the two offered by default - `DRAFT` & `PUBLISHED` - is available to paid plans. Upgrade your plan. ## Edit a content stage 1. Navigate to the content stage you want to edit. 2. Press "edit" in the lower left corner of the stage card. 3. Click `Update` in the lower right corner of the card. System content stages - `DRAFT` & `PUBLISHED` - cannot be edited. ## Delete a content stage 1. Navigate to the content stage card you want to delete. 2. Click `Delete` and confirm your choice. System content stages - `DRAFT` & `PUBLISHED` - cannot be deleted. ## Publishing content Hygraph automatically generates a publish mutation for each of your content models, including the asset model. If the models have localized fields, you can greater control the `locales` you want to publish, and if you want to publish the base entry. Each time you publish to a content stage, you will create a snapshot of the data as a [version](#versioning). For example, if we have a product model, the mutation `publishProduct` will exist. You can use this mutation to publish content to a content stage. | Argument | Input Tpye | Description | | ------------------- | ------------------------- | --------------------------------------------------------------------------------------------------- | | `where` | `ProductWhereUniqueInput` | The content entry you want to publish, using a [filter](/docs/api-reference/content-api/filtering). | | `to` | `[Stage!]! = [PUBLISHED]` | The target published content stage. | | `locales` | `[Locale!]` | Optional locales to publish. | | `publishBase` | `Boolean = true` | Whether to publish the base content entry. | | `withDefaultLocale` | `Boolean = true` | | ```graphql mutation { publishProduct(where: { id: "..." }, to: PUBLISHED) { id } } ``` ```graphql mutation { publishProduct( where: { id: "..." } to: PUBLISHED publishBase: true locales: [en] withDefaultLocale: true ) { id } } ``` Every mutation besides `publish` and `unpublish`, will only update the `DRAFT` version. For instance, when you update an entry in the UI, you are doing an `updateMutation`, which changes the `DRAFT` version. To update the published version, you would need to publish that `DRAFT`. ## Unpublishing content Similar to publishing content, you can unpublish from a selected content stage. For example, if we have a product model, the mutation `unpublishProduct` will exist. You can use this mutation to unpublish content from a content stage. | Argument | Input Tpye | Description | | --------------- | ------------------------- | ----------------------------------------------------------------------------------------------------- | | `where` | `ProductWhereUniqueInput` | The content entry you want to unpublish, using a [filter](/docs/api-reference/content-api/filtering). | | `from` | `[Stage!]! = [PUBLISHED]` | The target [content stage](/docs/api-reference/content-api/content-stages) to unpublish from. | | `locales` | `[Locale!]` | Optional locales to unpublish. | | `unpublishBase` | `Boolean = true` | Whether to unpublish the base content entry or not. | ```graphql mutation { unpublishProduct(where: { id: "..." }, from: PUBLISHED) { id } } ``` ```graphql mutation { unpublishProduct( where: { id: "..." } from: PUBLISHED unpublishBase: true locales: [en] ) { id } } ``` ## Versioning Versioning allows you to work non-destructively with content. Make changes, and recover previous versions with Hygraph. Each time you publish to a content stage, history is written. Versions of documents are kept for a minimum of 30 days, for paid plans. Upgrade your plan for longer history retention. ### Fetching version history You can fetch all of the history by `stage`. `history` is a [system field](/docs/api-reference/schema/system-fields#version-history-fields). For example, let's get all of the `history` for `products`. ```graphql { products(stage: PUBLISHED) { history { id stage revision createdAt } } } ``` ```json { "data": { "products": [ { "history": [ { "id": "ckdt47uio02al01044grc4ehf", "stage": "PUBLISHED", "revision": 4, "createdAt": "2020-11-02T10:28:24.273413+00:00" }, { "id": "ckdt47uio02al01044grc4ehf", "stage": "PUBLISHED", "revision": 3, "createdAt": "2020-11-02T10:01:40.453523+00:00" }, { "id": "ckdt47uio02al01044grc4ehf", "stage": "PUBLISHED", "revision": 2, "createdAt": "2020-11-02T10:01:36.351916+00:00" }, { "id": "ckdt47uio02al01044grc4ehf", "stage": "PUBLISHED", "revision": 1, "createdAt": "2020-11-02T10:00:22.14774+00:00" } ] } ] } } ``` Replace `stage: PUBLISHED` with `stage: QA` to get history from custom stage `QA`. ### Fetching a single version You can use a the generated `[model]Version` query to fetch a single version of a content entry. Since models can be complex, and change over time. The `data` field is a JSON field that returns a snapshot of the content entry at that time. Using the `id`, and `revision` from the previous query, we can fetch the `data` of the version, including the relations metadata. ```graphql { productVersion( where: { id: "ckdt47uio02al01044grc4ehf", revision: 4, stage: PUBLISHED } ) { id stage revision data } } ``` ```json { "data": { "productVersion": { "id": "ckdt47uio02al01044grc4ehf", "stage": "PUBLISHED", "revision": 4, "data": { "id": "ckdt47uio02al01044grc4ehf", "name": "Snapback", "slug": "snapback", "image": { "id": "ckdt47ocg02af0104ek5dinsj", "__typename": "Asset" }, "stage": "PUBLISHED", "prices": [ { "id": "cke1f4fow022h0151b46dd64d", "__typename": "Price" }, { "id": "cke1f4pq0022f0146btokysn8", "__typename": "Price" } ], "category": { "id": "cke1erm9s01ov0151p2h1k6bw", "__typename": "Category" }, "createdAt": "2020-08-13T18:07:36.201803+00:00", "updatedAt": "2020-08-19T13:35:19.916504+00:00", "description": { "raw": { "children": [ { "type": "paragraph", "children": [ { "text": "" } ] } ] }, "__typename": "RichText" }, "publishedAt": "2020-11-02T10:28:24.273413+00:00" } } } } ``` --- # Filtering Source: https://hygraph.com/docs/api-reference/content-api/filtering ## Overview Hygraph automatically creates filters for types you add to your content models. These filters can be applied to a single, or multiple entries, and nested object fields. The best place to explore all available filters is by using the [API Playground](/docs/api-reference/basics/api-playground). ## Using filters To filter content entries, simply pass the `where` argument to the query, followed by any of the [filter types](#filter-types) for the fields on your model. - All models come with their own custom GraphQL input type. Depending on the field type you want to filter by, there will be different fields you can filter by. String fields will behavior differently to Boolean fields for example. - **Array fields** only have `contains_all`, `contains_none`, `contains_some` filters available. - Check out the API Playground to see what types the `where` filter accepts for the fields in your schema. For example, a `Post` model will have the `where` input types `PostWhereInput` and `PostWhereUniqueInput` on the `posts`, and `postsConnection` query types. These types contain filters specific to that content type. Hygraph does not currently support filtering for Rich Text - even if inside components -, JSON, multi-value fields, colors, or coordinates. ## Filter types ### ID Entries can be filtered by `id`. | Matches | Type | Behavior | | -------------------- | ------- | ------------------------ | | `id` | `ID` | Equal to | | `id_not` | `ID` | Not this (null accepted) | | `id_in` | `[ID!]` | One of | | `id_not_in` | `[ID!]` | Not one of | | `id_starts_with` | `ID` | Starts with | | `id_not_starts_with` | `ID` | Does not start with | | `id_ends_with` | `ID` | Ends with | | `id_not_ends_with` | `ID` | Does not end with | | `id_contains` | `ID` | Contains | | `id_not_contains` | `ID` | Does not contain | ### String All String fields can be filtered using: | Matches | Type | Behavior | | ----------------------------- | ---------- | ------------------------- | | `[fieldName]_not` | `String` | Not this (null accepted) | | `[fieldName]_in` | `[String]` | One of | | `[fieldName]_not_in` | `[String]` | Not one of | | `[fieldName]_starts_with` | `String` | Starts with string | | `[fieldName]_not_starts_with` | `String` | Doesn't start with string | | `[fieldName]_ends_with` | `String` | Ends with string | | `[fieldName]_not_ends_with` | `String` | Doesn't end with string | | `[fieldName]_contains` | `String` | Includes string | | `[fieldName]_not_contains` | `String` | Does not include string | ### Integer All Integer fields can be filtered using: | Matches | Type | Behavior | | -------------------- | ------- | ------------------------ | | `[fieldName]_not` | `Int` | Not this (null accepted) | | `[fieldName]_in` | `[Int]` | One of | | `[fieldName]_not_in` | `[Int]` | Not one of | | `[fieldName]_lt` | `Int` | Less than | | `[fieldName]_gt` | `Int` | Greater than | | `[fieldName]_lte` | `Int` | Less than or equal to | | `[fieldName]_gte` | `Int` | Greater than or equal to | ```graphql { products(where: { quantity: 100 }) { quantity } multipleQuantities: products(where: { quantity_in: [10, 100, 1000] }) { quantity } } ``` ### Float All Float fields can be filtered using: | Matches | Type | Behavior | | -------------------- | --------- | ------------------------ | | `[fieldName]_not` | `Float` | Not this (null accepted) | | `[fieldName]_in` | `[Float]` | One of | | `[fieldName]_not_in` | `[Float]` | Not one of | | `[fieldName]_lt` | `Float` | Less than | | `[fieldName]_gt` | `Float` | Greater than | | `[fieldName]_lte` | `Float` | Less than or equal to | | `[fieldName]_gte` | `Float` | Greater than or equal to | ```graphql { products(where: { rating: 4.5 }) { name rating } } ``` ### Boolean All Booleans belonging to your content model can be filtered using the field name directly, as well as appended with `_not`, with a Boolean input type. | Matches | Type | Behavior | | ------------- | --------- | ------------------------------------------- | | `[field]` | `Boolean` | Is | | `[field]_not` | `Boolean` | Flips boolean (null not taken into account) | For example, let's filter posts where the custom field `verified` is `true`: ```graphql { posts(where: { verified: true }) { id } posts(where: { verified_not: true }) { id } } ``` ### Date All Date fields can be filtered using: | Matches | Type | Behavior | | -------------------- | -------- | ------------------------ | | `[fieldName]_not` | `Date` | Not this (null accepted) | | `[fieldName]_in` | `[Date]` | One of | | `[fieldName]_not_in` | `[Date]` | Not one of | | `[fieldName]_lt` | `Date` | Less than | | `[fieldName]_gt` | `Date` | Greater than | | `[fieldName]_lte` | `Date` | Less than or equal to | | `[fieldName]_gte` | `Date` | Greater than or equal to | ```graphql { today: events(where: { day: "2020-10-07" }) { day } upcoming: events(where: { day_gt: "2020-10-07" }) { day } } ``` ### DateTime Hygraph stores Date/DateTime fields as UTC strings, ISO 8601. Like Date fields, DateTime fields can be filtered using: | Matches | Type | Behavior | | -------------------- | ------------ | ------------------------ | | `[fieldName]_not` | `DateTime` | Not this (null accepted) | | `[fieldName]_in` | `[DateTime]` | One of | | `[fieldName]_not_in` | `[DateTime]` | Not one of | | `[fieldName]_lt` | `DateTime` | Less than | | `[fieldName]_gt` | `DateTime` | Greater than | | `[fieldName]_lte` | `DateTime` | Less than or equal to | | `[fieldName]_gte` | `DateTime` | Greater than or equal to | ```graphql { events(where: { start: "2020-10-07T09:00:00+00:00" }) { start } previous: events(where: { start_lt: "2020-10-07T09:00:00+00:00" }) { start } } ``` ### Basic Reference All relations (except Unions) can be filtered using filters on the fields of the model you are referencing. You can filter where every, some, and none at all match the conditions provided. | Matches | Behavior | | ------------------- | ----------------------- | | `[fieldName]_every` | Every reference matches | | `[fieldName]_some` | Some references match | | `[fieldName]_none` | No references match | For example, you could fetch every post by the provided author name. ```graphql { posts(where: { authors_every: { name_in: ["John", "Simona"] } }) { title authors { name } } } ``` #### Null references It is possible to filter on single, and multi reference fields for when these references are empty. ```graphql { posts(where: { author: null }) { id author { id } } } ``` ```graphql { posts(where: { author: {} }) { id author { id } } } ``` - `[fieldName]_every: {}`: Returns all authors, with or without connected posts - `[fieldName]_some: {}`: Returns all authors with at least one connected post - `[fieldName]_none: {}`: Returns all authors that have no posts connected ```graphql { authors(where: { posts_none: {} }) { id posts { id } } } ``` ```graphql { authors(where: { posts_some: {} }) { id posts { id } } } ``` ```graphql { authors(where: { posts_every: {} }) { id posts { id } } } ``` ### Union reference Using the `where` filter, you can filter by unions to get into the models and even fields inside of them. ```graphql query MyQuery { # Find posts where the postPageUnion field has entries connected posts(where: { postPageUnion_empty: false }) { id postPageUnion { __typename ... on Page { id path updatedAt } } } } ``` ```graphql { "data": { "posts": [ { "id": "clfuxrsxicbtn0bw80otuqvdc", "postPageUnion": [ { "__typename": "Page", "id": "clfusno48bgnj0bw8r09tkfmd", "path": "home", "updatedAt": "2023-03-30T09:51:30.842718+00:00" }, { "__typename": "Page", "id": "clfusoca3bfb20bw7qq5ppcs7", "path": "features", "updatedAt": "2023-03-30T10:40:38.541517+00:00" }, { "__typename": "Page", "id": "clfusouvbbbob0bulri695oa1", "path": "404", "updatedAt": "2023-03-30T10:53:26.196075+00:00" } ] }, { "id": "clfuzysanci8n0bw7y29vd16n", "postPageUnion": [ { "__typename": "Page", "id": "clfusouvbbbob0bulri695oa1", "path": "404", "updatedAt": "2023-03-30T10:53:26.196075+00:00" }, { "__typename": "Page", "id": "clfusqhb8bbyt0buljnh37oxd", "path": "new", "updatedAt": "2023-03-30T10:54:59.459294+00:00" } ] } ] } } ``` You can combine the `where` filter with the following: | Matches | Type | Behavior | | ------------------- | --------- | ---------------------------------------------------------------------------------------- | | `[FieldName]_empty` | `Boolean` | If true, it returns every result where the filtered union is not connected / is empty | | `[FieldName]_some` | `Array` | Matches if the union contains at least one connection to the item provided to the filter | - `_empty` and `_some` can be used to query union fields that allow multiple values. - The filter does not modify the query, but will simply select a subset of entries from the results returned by that query, based on the filter. ```graphql query MyQuery { # Find pages where no sections are connected pages(where: {sections_empty: true}) { id sections { __typename ... on TextComponent { heading } ... on GalleryComponent { heading } ... on Testimonial { customerName } } } } ``` ```graphql query MyQuery { # Find pages where the testimonial is from Hygraph pages(where: {sections_some: {Testimonial: {customerName_contains: "Hygraph"}}}) { id sections { __typename ... on TextComponent { heading } ... on GalleryComponent { heading } ... on Testimonial { customerName } } } } ``` ```graphql # Union reference doesn't allow multiple values # Find pages where the section union testimonial's customer name contains "Hygraph" query MyQuery { pages(where: {section: {Testimonial: {customerName_contains: "Hygraph"}}}) { id } } ``` #### Nested unions Using filters allows going into the reverse field and getting information from the start model. ```graphql query MyQuery { # Find pages where the Testimonial List contains Hygraph pages( where: { sections_some: { # Union relational field TestimonialList: { # The linked component customerList_some: { # Basic relational field on the component name: "Hygraph" # Field we are filtering for } } } } ) { id sections { __typename ... on TextComponent { heading } ... on GalleryComponent { heading } ... on Testimonial { customerName } } seo { metaTitle } } } ``` ```graphql { "data": { "pages": [ { "id": "clfusno48bgnj0bw8r09tkfmd", "sections": [ { "__typename": "TextComponent", "heading": "Welcome" }, { "__typename": "GalleryComponent", "heading": "Product Pictures" }, { "__typename": "Testimonial", "customerName": "Hygraph" }, { "__typename": "TestimonialList" } ], "seo": { "metaTitle": "Homepage" } }, { "id": "clfusoca3bfb20bw7qq5ppcs7", "sections": [ { "__typename": "TextComponent", "heading": "Feature Showcase" }, { "__typename": "GalleryComponent", "heading": "Pictures" }, { "__typename": "TestimonialList" } ], "seo": { "metaTitle": "Hygraph" } } ] } } ``` ### Basic component #### Basic component field allows multiple values All basic component fields that allow multiple values can be filtered using: | Matches | Behaviour | | ------------------- | ----------------------- | | `[fieldName]_every` | Every reference matches | | `[fieldName]_some` | Some references match | | `[fieldName]_none` | No references match | ```graphql query MyQuery { # Finds pages where all Post component paths contain "example" pages(where: {posts_every: {path_contains: "example"}}) { id title path } } ``` ```graphql query MyQuery { # Finds pages where at least one Post component path contains "example" pages(where: {posts_some: {path_contains: "example"}}) { id title path } } ``` ```graphql query MyQuery { # Finds pages where none of the Post component paths contain "example" pages(where: {posts_none: {path_contains: "example"}}) { id title path } } ``` #### Basic component field doesn't allow multiple values If the basic component field does not allow multiple values, the `_every`, `_some`, and `_none` filters are unavailable, but you can query for a match as well as for empty components. ```graphql query MyQuery { # Find pages where the seo field is empty (basic component field) pages(where: {seo: {}}) { id sections { __typename ... on TextComponent { heading } ... on GalleryComponent { heading } ... on Testimonial { customerName } } seo { metaTitle } } } ``` ```graphql query MyQuery { # Find pages where the Seo components field metatitle contains "Home" pages(where: {seo: {metaTitle_contains: "Home"}}) { id sections { __typename ... on TextComponent { heading } ... on GalleryComponent { heading } ... on Testimonial { customerName } } seo { metaTitle } } } ``` ### Modular component All modular component fields that allow multiple values can be filtered using: | Matches | Type | Behavior | | ------------------- | --------- | ---------------------------------------------------------------------------------------- | | `[FieldName]_empty` | `Boolean` | If true, it returns every result where the filtered model is empty | | `[FieldName]_some` | `Array` | Matches if the model contains at least one connection to the item provided to the filter | ```graphql query MyQuery { # Find the id of pages where the sections component is not empty pages(where: {sections_empty: false}) { id } } ``` ```graphql # Find pages that contain at least one connection to the sections component query MyQuery { pages(where: {sections_some: { # Modular component field TextComponent: { # The linked component heading_contains: "showcase" # The field that we are filtering for } } }) { id sections { __typename ... on TextComponent { heading body } } } } ``` ```graphql # Modular component doesn't allow multiple values # Finds pages where the Section component TextComponent's heading matches "test" query MyQuery { pages (where: {section: {TextComponent: {heading: "test"}}}) { id } } ``` #### Nested components You can use the above filters to filter data from different levels of nested components as follows: ```graphql query MyQuery { # Find pages where the Testimonial List contains Hygraph pages( where: { sections_some: { # Modular component field TestimonialList: { # The linked component customerList_some: { # Basic component field on the component name: "Hygraph" # Field we are filtering for } } } } ) { id sections { __typename ... on TextComponent { heading } ... on GalleryComponent { heading } ... on Testimonial { customerName } } seo { metaTitle } } } ``` ```graphql { "data": { "pages": [ { "id": "clfusno48bgnj0bw8r09tkfmd", "sections": [ { "__typename": "TextComponent", "heading": "Welcome" }, { "__typename": "GalleryComponent", "heading": "Product Pictures" }, { "__typename": "Testimonial", "customerName": "Hygraph" }, { "__typename": "TestimonialList" } ], "seo": { "metaTitle": "Homepage" } }, { "id": "clfusoca3bfb20bw7qq5ppcs7", "sections": [ { "__typename": "TextComponent", "heading": "Feature Showcase" }, { "__typename": "GalleryComponent", "heading": "Pictures" }, { "__typename": "TestimonialList" } ], "seo": { "metaTitle": "Hygraph" } } ] } } ``` ### Enumeration All Enum fields can be filtered by using: | Matches | Type | Behavior | | -------------------- | -------------------- | ------------------------ | | `[fieldName]_not` | `EnumerationValue` | Not this (null accepted) | | `[fieldName]_in` | `[EnumerationValue]` | One of | | `[fieldName]_not_in` | `[EnumerationValue]` | Not one of | The type of enumeration you can filter by will be the actual Enumeration values defined in your schema. ```graphql { resources(where: { type_in: [Webinar, Ebook] }) { id } } ``` `Webinar` and `Ebook` are Enumeration values for field `type`. ### Taxonomy All Taxonomy fields can be filtered by using: | Matches | Type | Behavior | |-----------------------------|------------------------------|--------------------------------------------------------------------------| | `[fieldName]_not` | `TaxonomyNodeWhereInput` | Not this (null accepted) | | `[fieldName]_in` | `[TaxonomyNodeWhereInput!]` | One of | | `[fieldName]_not_in` | `[TaxonomyNodeWhereInput!]` | Not one of | | `[fieldName]_contains_all` | `[TaxonomyNodeWhereInput!]` | Contains all specified values | | `[fieldName]_contains_some` | `[TaxonomyNodeWhereInput!]` | Contains at least one of the specified values | | `[fieldName]_contains_none` | `[TaxonomyNodeWhereInput!]` | Contains none of the specified values | | `[fieldName]_descendants_of`| `[TaxonomyNodeWhereInput!]` | Returns the specified taxonomy node and all nodes below it (descendants) | You can filter the taxonomy by the taxonomy node names defined in your schema. ```graphql query MyQuery { productPages(where: {category_descendants_of: {value: "Pants"}}) { id name category { value path { value } } } } ``` ```graphql { "data": { "productPages": [ { "id": "cmdiq25yg02s307ulnc7iuah8", "name": "Casual pants", "category": { "path": [ { "value": "Clothes" }, { "value": "Women" }, { "value": "Pants" }, { "value": "Casual" } ], "value": "Casual" } }, { "id": "cmdiu3er303kf08upeajplqbq", "name": "Formal pants", "category": { "path": [ { "value": "Clothes" }, { "value": "Women" }, { "value": "Pants" }, { "value": "Formal" } ], "value": "Formal" } }, { "id": "cmdiu3qse03m607ulk10sg0hu", "name": "Jeans", "category": { "path": [ { "value": "Clothes" }, { "value": "Women" }, { "value": "Pants" }, { "value": "Jeans" } ], "value": "Jeans" } } ] } } ``` ### Asset All Asset fields can be filtered using: | Matches | Type | Behavior | | -------------------- | --------------- | ------------------------ | | `[fieldName]_not` | `String` | Not this (null accepted) | | `[fieldName]_in` | `[String]` | One of | | `[fieldName]_not_in` | `[String]` | Not one of | | `[fieldName]_lt` | `String` | Less than | | `[fieldName]_gt` | `String` | Greater than | | `[fieldName]_lte` | `String` | Less than or equal to | | `[fieldName]_gte` | `String` | Greater than or equal to | | `[fieldName]_every` | _Relation Type_ | Every reference matches | | `[fieldName]_some` | _Relation Type_ | Some references match | | `[fieldName]_none` | _Relation Type_ | No references match | Asset fields come with their own [System Fields](/docs/api-reference/schema/system-fields#asset-fields) which you can apply these filters on, as well as any custom fields, or references you add. You can filter the asset through the reference, or when querying all assets. For example, we could fetch posts where the `coverImage` field meets the provided criteria on the system field `fileName`: ```graphql { posts(where: { coverImage: { fileName: "image.png" } }) { id coverImage { fileName } } } ``` ## Combining filters Just like [combining query arguments](/docs/api-reference/content-api/queries#combining-arguments), it is also possible to combine filters. ```graphql { events( where: { start_gt: "2020-10-01T09:00:00+00:00" start_lt: "2020-10-31T09:00:00+00:00" fancyDress: true price: 100 } ) { start fancyDress price } previous: events(where: { start_lt: "2020-10-07T09:00:00+00:00" }) { start } } ``` ## Conditional filters Hygraph supports conditional filters for your content using `AND`, `NOT` and `OR`. Useful for filtering results based on more than one criteria. Conditional filters are a way to logically apply conditions of the applicable filters above. They can also be nested. | Input Type | Description | | ---------- | ---------------------------------------------------- | | `AND` | Fetch entries that meet both conditions. | | `OR` | Fetch entries that match either condition. | | `NOT` | Fetch all entries where the conditions do not match. | ```graphql { events( where: { AND: [{ start_gte: "2020-10-01" }, { start_lte: "2020-10-31" }] } ) { id start } } ``` ```graphql { events(where: { OR: [{ free: true }, { start_gt: "2020-10-31" }] }) { id start } } ``` ```graphql { events(where: { NOT: [{ free: true }, { start_gt: "2020-10-31" }] }) { id start } } ``` ```graphql { events( where: { OR: [ { AND: [{ start_gte: "2020-10-01" }, { start_lte: "2020-10-31" }] } { id: "..." } ] } ) { id start } } ``` ## Filter by locales When querying content entries, you can also filter by `locales`: ```graphql { posts(locales: [en]) { id } } ``` Learn more about [localization](/docs/api-reference/content-api/localization). ## Filter by stage argument When querying content entries, you can also filter by `stage`. The stage argument decides what document variation gets returned and searched through. Therefore, if the document does not exist in the stage variation, it gets filtered out. ```graphql { posts(stage: PUBLISHED) { id stage } } ``` Learn more about [content stages](/docs/api-reference/content-api/content-stages). ## Filter by content stage Stages work a bit differently in the API than in the UI. In the UI, we use the most intuitive and editor-friendly way of treating filters. So, for instance, if you see a green `PUBLISHED` pill next to an entry, then this entry is only in the `PUBLISHED` stage. However, in the backend, our stages are organized slightly differently. The main difference is that each content entry always exists in `DRAFT`, and other stages are added or removed. For example, an entry that has a green `PUBLISHED` pill in the UI is actually in both `DRAFT` and `PUBLISHED` stages on the API side, and both versions of the entry are identical. If you update the `DRAFT` version but don't publish it, the entry will continue to exist in both stages, but will be marked as blue `PUBLISHED` in the UI. Stages can be filtered using: | Matches | Behavior | | ------------------------ | ------------------------------------------------------------------------- | | `documentInStages_every` | All existing stage variations must match the sub-filter | | `documentInStages_some` | At least one of the existing stage variations must match the sub-filter | | `documentInStages_none` | None of the existing stage variations are allowed to match the sub-filter | To summarize, like we mentioned before, a document will always exist in the `DRAFT` stage, and they may or may not exist in other published stages such as `PUBLISHED` or for example `QA` - which is a custom content stage that can be published to. If you, as a user, want to find documents that exist in the `PUBLISHED` stage, you can run the following query: ```graphql stage: DRAFT where: { documentInStages_some: { stage: PUBLISHED } } ``` The above `documentInStages_some` allows the user to find documents which exist in a different stage. Let's consider the following 3 documents, which exist in the following stages: | ID | STAGES | | ----------- | ---------------------- | | cldocument1 | [DRAFT, PUBLISHED] | | cldocument2 | [DRAFT] | | cldocument4 | [DRAFT, PUBLISHED, QA] | The above query will return documents that also exist in the `PUBLISHED` stage, which are `cldocument1` and `cldocument4`. However you may have noticed that `cldocument4` also has been published to the `QA` stage - If you have access to custom stages. Imagine you want to query documents which exist only in one published stage `PUBLISHED`, but not any other publishable stages. In this case the result you want is `cldocument1`. For this we can use `documentInStages_every`. Keeping in mind that a document entry can exist in multiple stages, the difference between `documentInStages_some` and `documentInStages_every` is that `documentInStages_some` checks if an entry exists in a particular stage, whereas by using `documentInStages_every` we request an entry that exists in only and exactly that stage. So if we use `documentInStages_every` with `PUBLISHED` the query will return results only if the document exists in `PUBLISHED` and no other stage. You might read this and try a query such as the following: ```graphql where: { documentInStages_every: { stage: PUBLISHED } } ``` While you are on the right track, this will unfortunately return no results. The reason is that, as we mentioned earlier, every document **always** exists in `DRAFT`, so no document will ever only exist in `PUBLISHED`. If you try changing the above `PUBLISHED` to `DRAFT`, however, you will get `cldocument2` as a result, as this document only exists in the `DRAFT` stage. As a workaround for this problem, we can use the `OR` meta filter, by using the following query: ```graphql where: { documentInStages_every: { OR: [ { stage: DRAFT }, { stage: PUBLISHED } ] } } ``` Since the internal query is that the stage must exist either in `DRAFT` or `PUBLISHED`, the query evaluates to true for both document `cldocument1` and `cldocument2`. It skips `cldocument4`, which is what we want, but we still get `cldocument2` which exists only in `DRAFT`. We can use the same pattern of using meta filters to filter out the `DRAFT` entry. Let's start by writing this query: ```graphql where: { NOT: [ { documentInStages_every: { stage: DRAFT } } ] } ``` As you can see, this query returns any document that does not exist _only_ in `DRAFT`, Which in this case would be `cldocument1` and `cldocument4`. If we were to do an intersection of the results of the above two queries, we would get `cldocument1`, which is the document that we want. In order to do this, we combine the above two queries to create the following: ```graphql where: { AND: [ { documentInStages_every: { OR: [ { stage: DRAFT }, { stage: PUBLISHED } ] } }, { NOT: [ { documentInStages_every: { stage: DRAFT } } ] } ] } ``` As you can see above, by using the `AND` meta filter we get the intersection between the two queries above, which returns the `cldocument1` document. **compareWithParent** `compareWithParent` allows the comparison of a document with its parent entry using any comparison operators available within it. At the moment `compareWithParent` is available only inside a `documentInStages_*` filter, and only allows the use of one attribute which is `outdated_to`. This attribute can be found inside the `compareWithParent` filter. The attribute is used to specify if we wish to check whether the child document is outdated compared to the parent document. For example: ```graphql variousDocuments( stage: DRAFT where: { documentInStages_some: { stage: PUBLISHED compareWithParent: { outdated_to: true } } } ) ``` In the above example, we enable the filter by setting `outdated_to` to **true** - if it were set to **false**, it would be the same as having the filter omitted entirely. The **true** and **false** values are simply indicators of whether to process the filter in the query. In the above query, we check if the child `PUBLISHED` version is outdated compared to the `DRAFT` parent version. At the moment, we check if a document is outdated by comparing the `updatedAt` attributes of both the `DRAFT` and `PUBLISHED` versions. If the `DRAFT` has a greater `updatedAt`, that means the document has been modified since it was last published, and is therefore considered outdated. ## JSON filtering You can filter the data in the `JSON` fields of your Hygraph project using the following filters. ### value_recursive You can filter the data in your `JSON` fields by using the following `value_recursive` syntax to find exact matches: ```graphql query for { posts(where: { jsonField_value_recursive: "hallo" }) { id jsonField } } ``` The query above would search the `JSON` fields for an exact match of **"hallo"** on all values, excluding the json keys. Using this filter, you can easily filter the content, even if you're not familiar with the `json_path` syntax. ### json_path_exists You can filter the data in your `JSON` fields by using `json_path_exists` syntax from PostgreSQL. To be able to query this content, you need to know the `JSON` structure to navigate through it. If you're not familiar with the structure, you can query for all content - without passing a filter, or with just a pagination filter depending on number of entries - and look into it. A great way to go about this kind of exploration is using the **API Playground**, where you can type **“json”** then use `ctrl + space` or `command + space` to display all the filters you can use and select one. Please note that if the content data is in a JSON field that allows multiple values, the query will return the complete list. #### Query syntax examples Imagine we have the following content data in a `JSON` field which allows multiple values: ```json "data": { "testFieldsModels": [ { "listJsonField": [ { "items": { "qty": 24, "product": "Diaper" }, "customer": "Lily Bush" }, { "items": { "qty": 1, "product": "Toy Car" }, "customer": "Josh William" }, { "items": { "qty": null, "product": "Headphones" }, "customer": "James Buyer" } ] } ] }, ``` Using the above sample content data, here are some query examples: - [Query by quantity](/docs/api-reference/content-api/filtering#query-by-quantity) - [Query by string](/docs/api-reference/content-api/filtering#query-by-string) - [Query by `null`](/docs/api-reference/content-api/filtering#query-by-null) - [Query by range](/docs/api-reference/content-api/filtering#query-by-range) Please note that in the query examples below, we escape the quotes with a backslash, like this `\”`. This is because the query is already inside double quotes, so any additional quotes require this format.   **Query by quantity** You can use `"$[*].** ? (@ > N)”` to filter all values all for integers above a certain number. Just replace `N` with the number of your choice. ```graphql query for { testFieldsModels( where: { listJsonField_json_path_exists: "$[*].items.qty ? (@ > 5)" } ) { listJsonField } } ``` The above query example returns products with a quantity greater than 5. **You can use:** - `>`: Greater than - `>=`: Greater than or equal to - `<`: Less than - `<=`: Less than or equal to   **Query by string** You can use `"$.** ? (@ == \"String\")”` to filter all values all for the matching string: ```graphql query for { testFieldsModels( where: { listJsonField_json_path_exists: "$.items.product ? (@ == \"Toy Car\")" } ) { listJsonField } } ``` The above query example returns products with the name **“Toy Car”**.   **Query by `null`** You can use `"$[*].** ? (@ == null)”` to filter all values all for null. ```graphql query for { testFieldsModels( where: { listJsonField_json_path_exists: "$[*].items.qty ? (@ == null)" } ) { listJsonField } } ``` The above query example returns items where quantity (qty) = `null`.   **Query by range** You can use `"$[*].** ? (@ > N && @ < N)”` to filter all values all for a range of numbers. Just replace `N` with the numbers of your choice. ```graphql query for { testFieldsModels( where: { listJsonField_json_path_exists: "$[*].items.qty ? (@ > 1 && @ < 5)" } ) { listJsonField } } ``` The above query example returns items with a quantity between 1 and 5. **You can use:** - `>`: Greater than - `>=`: Greater than or equal to - `<`: Less than - `<=`: Less than or equal to --- # Headers Source: https://hygraph.com/docs/api-reference/content-api/headers ## Overview HTTP headers are a set of key-value pairs included in HTTP requests. They are essential for communicating extra information between the client (you) and the server. When making GraphQL queries over HTTP, headers are typically used to send important details, like authentication info, content type information, or custom information needed by the server. Think of HTTP headers as “metadata” for your request, as they carry information about the request itself rather than the actual query content. Each header has a name (like `"Authorization"`) and a value (like `"Bearer YOUR_TOKEN_HERE"`). For example: ``` Authorization: Bearer YOUR_TOKEN_HERE Content-Type: application/json ``` ## HTTP headers in GraphQL These are the most commonly used headers in GraphQL: - **Authorization**: You need an authorization token for secure API access in Hygraph. You'll use **HTTP headers** to pass authentication tokens, specifically the `Authorization` header with a **Bearer token**. This process ensures that only authorized users or systems can access your data. - For example: `Authorization: Bearer YOUR_TOKEN_HERE` - **Content-Type**: This header tells the server what format the request body is in. In GraphQL, it's usually JSON. - For example: `Content-Type: application/json` - **Custom Headers**: If you need to send any specific info to the server, you can add your own headers. The [custom headers](/docs/api-reference/content-api/headers#custom-headers) shown in this document, are headers that Hygraph specifically uses to control aspects related to how the data should be fetched. While they follow the general structure of HTTP headers, they are unique to Hygraph's API and not standard HTTP headers like `Authorization` or `Content-Type`. - For example: `gcms-stage: DRAFT` ### Add headers to a GraphQL query The way that you add headers will depend on the tool or language that you're using. Here's how you'd typically add headers in two common ways: using `cURL` (a command-line tool) and `JavaScript` (fetch API). Example with `cURL`: ```bash curl -X POST -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_TOKEN_HERE" \ -d '{"query": "{ yourQueryHere }"}' \ https://your-graphql-api.com/graphql ``` Example with `JavaScript`: ```jsx fetch("https://your-graphql-api.com/graphql", { method: "POST", headers: { "Content-Type": "application/json", "Authorization": "Bearer YOUR_TOKEN_HERE" }, body: JSON.stringify({ query: "{ yourQueryHere }" }) }) .then(response => response.json()) .then(data => console.log(data)); ``` The `headers` object shown in of both examples above is where we specify the `HTTP` headers. Each header will let the server understand the context of your request. ## Custom headers You can use headers as a global way to send more specific requests to our API. While you could use parameters for this, it is generally easier to dynamically change headers than parameters in a query. This is where you add headers in the API Playground: ![Headers in the API Playground](/images/docs/api-reference/content-api/headers-api-playground.png) ### gcms-locales This header works in the same way as [using the query input for locales](/docs/api-reference/content-api/localization#fallback-locales). You can pass the `gcms-locales` header for localized content with your required locales. It is just another way besides the query param to decide which locales a query should return. Basically, the placement is different - header vs. query - but the result is the same. Using one or the other mostly comes down to the developer's preference. **Why use `gmcs-locales` instead of the query param then?** To keep queries exactly the same across different environments - production, development, etc. - and then instead of passing the param to the query, you use the header. In some specific cases passing headers could be easier than putting the params in the query itself. Example usage `'gcms-locales': 'rb, de, en'`: ```js const fetch = require('cross-fetch'); const headers = { 'Content-Type': 'application/json', 'gcms-locales': 'rb, de, en', }; const body = JSON.stringify({ query: '{ products { name } }' }); const { products } = await fetch('', { method: 'POST', headers, body, }); ``` You can also pass an array of locales to `gcms-locales` to define your fallback preference. ### gcms-stage This header works in the same way as [using the stage input on a query](/docs/api-reference/content-api/queries#fetching-stages). You can pass the `gcms-stage` header to specify the stage of your content. It is just another way besides the query param to decide which stage a query should return. Basically, the placement is different - header vs. query - but the result is the same. Using one or the other mostly comes down to the developer's preference. **Why use `gcms-stage` instead of the query param then?** To keep queries exactly the same across different environments - production, development, etc. - and then instead of passing the param to the query, you use the header. In some specific cases passing headers could be easier than putting the params in the query itself. Example usage `'gcms-stage': 'DRAFT'`: ```js const fetch = require('cross-fetch'); const headers = { 'Content-Type': 'application/json', 'gcms-stage': 'DRAFT', }; const body = JSON.stringify({ query: '{ products { name } }' }); const { products } = await fetch('', { method: 'POST', headers, body, }); ``` ### hyg-stale-if-error This header for the [High performance endpoint](/docs/api-reference/basics/caching#stale-if-error) lets you set `stale-if-error` on a per query basis. This cache header can be used to fine-tune our default caching behavior. For instance, we could use it to make sure we return stale data on errors for much longer. The need to use this header highly depends on the use-case and whether it's important to always get fresh data or to always get data, no matter if stale or new. Example usage `'hyg-stale-if-error': '21600'`: ```js const fetch = require('cross-fetch'); const headers = { 'Content-Type': 'application/json', 'hyg-stale-if-error': '21600', }; const body = JSON.stringify({ query: '{ products { name } }' }); const { products } = await fetch('', { method: 'POST', headers, body, }); ``` The values are in seconds. The default value is `86400`, but this can be adjusted **on dedicated clusters** if needed. This is only available on the High performance endpoint. The default `Stale-if-error` for all **shared clusters** is 86400s (1 day). This value is customizable for **dedicated clusters**. To check what your current default is, send a request to the [high-performance endpoint](/docs/api-reference/basics/caching#high-performance-endpoint) and check the response headers called `gcms-origin-cache-control`. On a shared cluster, the value of the response header would be similar to this `s-maxage=31536000, stale-if-error=86400, stale-while-revalidate=0`. ### hyg-stale-while-revalidate This header for the [High performance endpoint](/docs/api-reference/basics/caching#stale-while-revalidate) lets you set `stale-while-revalidate` on a per query basis. This cache header can be used to fine-tune our default caching behavior. For instance, we could use it to make sure we return stale data while the cache is revalidating. The need to use this header highly depends on the use-case and whether it's important to always get fresh data or to always get data, no matter if stale or new. Example usage `'hyg-stale-while-revalidate': '27'`: ```js const fetch = require('cross-fetch'); const headers = { 'Content-Type': 'application/json', 'hyg-stale-while-revalidate': '27', }; const body = JSON.stringify({ query: '{ products { name } }' }); const { products } = await fetch('', { method: 'POST', headers, body, }); ``` The values are in seconds. The default value is `0`, but this can be adjusted **on dedicated clusters** if needed. This is only available on the High performance endpoint. The default `stale-while-revalidate` for our **shared clusters** is `0`. This value is customizable for **dedicated clusters**. To check what your current default is, send a request to the [high-performance endpoint](/docs/api-reference/basics/caching#high-performance-endpoint) and check the response headers called `gcms-origin-cache-control`. On a shared cluster, the value of the response header would be similar to this `s-maxage=31536000, stale-if-error=86400, stale-while-revalidate=0`. ### x-debug-complexity You can use this header to enable complexity debugging, so the [complexity tree](/docs/api-reference/basics/query-complexity#complexity-tree-json-output) is returned. The possible values are `true` & `false`. Example usage `'x-debug-complexity': 'true'`: ```js const fetch = require('cross-fetch'); const headers = { 'Content-Type': 'application/json', 'x-debug-complexity': 'true', }; const body = JSON.stringify({ query: '{ products { name } }' }); const { products } = await fetch('', { method: 'POST', headers, body, }); ``` --- # Localization Source: https://hygraph.com/docs/api-reference/content-api/localization ## Overview Hygraph boasts a flexible localization API that you can use to publish content for all or specific locales in your project. **Locales are environment specific**. This means their configuration is applied per environment. Take this into consideration if you're working with a project using more than one environment. Localized content can be managed through the Hygraph UI, or via GraphQL mutations. ## Localizing fields When adding a field that can be localized inside the schema builder, such as a string, mark the field as can be localized, and you will be able to perform all of the localization queries, and mutations outlined below. ![Hygraph Localize Fields](/images/docs/api-reference/content-api/localize-field.png) The model will be updated to contain additional [localized system fields](/docs/api-reference/schema/system-fields#localization-fields) that you can use to fetch localized content. ## Fetching localized content Fetching localized content is done by [fetching content](/docs/api-reference/content-api/queries) the same way you are used to. For example, a product model containing localized fields can be queried like so: ```graphql { product(where: { id: "..." }) { name } products { name } } ``` The above will return the default locale values for the fields requested. ### Default locale As shown as above, queried content will always return the default locale, unless told otherwise. You can set the default locale inside `Settings > Locales`. ![Hygraph default locale](/images/docs/api-reference/content-api/default-locale.png) ### Fallback locale(s) Locales will be returned in the order they are requested, from left to right. In this example, we will request products with the locales `en`, and `de`. ```graphql { products(locales: [en, de]) { id locale name } } ``` ```json { "data": { "products": [ { "id": "ck3ee0ltu3kfn0b73wbslopia", "locale": "en", "name": "Unisex Long Sleeve Tees" }, { "id": "ck3itjds7hnwo0b66au5rkvm9", "locale": "en", "name": "Snapback" }, { "id": "ck3oqti2stn5o0b20ogmpwxer", "locale": "en", "name": "Unisex Zip Hoodie" }, { "id": "ck3shntzy1xxw0b324jfxcjtl", "locale": "en", "name": "Mug" } ] } } ``` ### HTTP header You can pass the `gcms-locales` header when localized content with your required locales. ```js const fetch = require('cross-fetch'); const headers = { 'Content-Type': 'application/json', 'gcms-locales': 'en', }; const body = JSON.stringify({ query: '{ products { name } }' }); const { products } = await fetch('', { method: 'POST', headers, body, }); ``` You can also pass an array of locales to `gcms-locales` to define your fallback preference. ### All localizations Whether you're fetching a [single content entry](/docs/api-reference/content-api/queries#fetching-a-single-entry), or [multiple](/docs/api-reference/content-api/queries#fetching-multiple-entries), you can fetch all `localizations` of that entry. ```graphql { products { id name localizations { id name locale } } } ``` ```json { "data": { "products": [ { "id": "ck3ee0ltu3kfn0b73wbslopia", "name": "Unisex Long Sleeve Tees", "localizations": [ { "id": "ck3ee0ltu3kfn0b73wbslopia", "name": "Unisex Longsleeve", "locale": "de" } ] }, { "id": "ck3itjds7hnwo0b66au5rkvm9", "name": "Snapback", "localizations": [ { "id": "ck3itjds7hnwo0b66au5rkvm9", "name": "Cap", "locale": "de" } ] }, { "id": "ck3oqti2stn5o0b20ogmpwxer", "name": "Unisex Zip Hoodie", "localizations": [ { "id": "ck3oqti2stn5o0b20ogmpwxer", "name": "Unisex Hoodie", "locale": "de" } ] }, { "id": "ck3shntzy1xxw0b324jfxcjtl", "name": "Mug", "localizations": [ { "id": "ck3shntzy1xxw0b324jfxcjtl", "name": "Tasse mit Print", "locale": "de" } ] } ] } } ``` Since the root graphql type returns the default locale `en` values, you'll notice the `localizations` response above returns just the `de` content entries. Pass `includeCurrent: true` inside the `localizations` query arguments to include the default `en` locale. ```graphql { products { id name localizations(includeCurrent: true) { id name locale } } } ``` ```json { "data": { "products": [ { "id": "ck3ee0ltu3kfn0b73wbslopia", "name": "Unisex Long Sleeve Tees", "localizations": [ { "id": "ck3ee0ltu3kfn0b73wbslopia", "name": "Unisex Long Sleeve Tees", "locale": "en" }, { "id": "ck3ee0ltu3kfn0b73wbslopia", "name": "Unisex Longsleeve", "locale": "de" } ] }, { "id": "ck3itjds7hnwo0b66au5rkvm9", "name": "Snapback", "localizations": [ { "id": "ck3itjds7hnwo0b66au5rkvm9", "name": "Snapback", "locale": "en" }, { "id": "ck3itjds7hnwo0b66au5rkvm9", "name": "Cap", "locale": "de" } ] }, { "id": "ck3oqti2stn5o0b20ogmpwxer", "name": "Unisex Zip Hoodie", "localizations": [ { "id": "ck3oqti2stn5o0b20ogmpwxer", "name": "Unisex Zip Hoodie", "locale": "en" }, { "id": "ck3oqti2stn5o0b20ogmpwxer", "name": "Unisex Hoodie", "locale": "de" } ] }, { "id": "ck3shntzy1xxw0b324jfxcjtl", "name": "Mug", "localizations": [ { "id": "ck3shntzy1xxw0b324jfxcjtl", "name": "Mug", "locale": "en" }, { "id": "ck3shntzy1xxw0b324jfxcjtl", "name": "Tasse mit Print", "locale": "de" } ] } ] } } ``` This will return all locales where values present. If nothing is found, the default locale will be returned. ## Mutating localized content Just like you can query localized content, you can also create, update, upsert, and delete localized content using the [Mutations API](/docs/api-reference/content-api/mutations). Let's continue with the products model in our examples below. We can use the [auto-generated mutations](/docs/api-reference/content-api/mutations#auto-generated-mutations) for modifying the locales for each entry. ### Create a localization ```graphql mutation { updateProduct( where: { id: "..." } data: { localizations: { create: { locale: de, data: { name: "Tasse mit Print" } } } } ) { id } } ``` You'll notice above we are using the `update[Model]` mutation to "create" a locale. This is because the existing entry is the default locale, and you can create additional localized content for fields on that entry. You can also pass an array of localizations for locales that aren't your projects default. For example, here we create localizations for both `de`, and `es`. ```graphql mutation { updateProduct( where: { id: "..." } data: { localizations: { create: [ { locale: de, data: { name: "Tasse mit Print" } } { locale: es, data: { name: "Taza con estampado" } } ] } } ) { id } } ``` The examples here assume you already have a product that you can update. It is possible to create a new entry, with the base locale, and the localized content via `localizations` at the same time: ```graphql mutation { createProduct( data: { name: "Mug" localizations: { create: [ { locale: de, data: { name: "Tasse mit Print" } } { locale: es, data: { name: "Taza con estampado" } } ] } } ) { id localizations(includeCurrent: true) { title } } } ``` ### Update a localization ```graphql mutation { updateProduct( where: { id: "..." } data: { localizations: { update: { locale: en, data: { name: "Mug" } } } } ) { id } } ``` ### Upsert a localization Just like you can create, or update content entries by unique filters, you can either also upsert localizations. Simply pass the `locale` you want to update, or create if it does not exist. ```graphql mutation { createProduct( where: { id: "..." } data: { localizations: { upsert: { locale: de create: { name: "Tasse mit Print" } update: { name: "Tasse mit Print" } } } } ) { id } } ``` ### Delete a localization To delete a localization, you need to use an update mutation, like so: ```graphql mutation { updatePost( where: { id: "..." } data: { localizations: { delete: [de] } } ) } ``` It is not possible to delete the default localization. --- # Mutations Source: https://hygraph.com/docs/api-reference/content-api/mutations Your project endpoint exposes GraphQL mutations you can use to modify the contents of your project. The mutations API allows you to interact with content ouside of the Hygraph UI using GraphQL. It's not recommended you enable Public API Permissions for mutations, but instead use a [Permanent Auth Token](/docs/api-reference/basics/authorization#permanent-auth-tokens) for mutating data. ## Auto-generated mutations When a new model is added to your project, so are custom GraphQL mutations. For example, if you created a `Product` model, these mutations would also be generated inside your GraphQL schema: - `createProduct` - `updateProduct` - `deleteProduct` - `upsertProduct` - `publishProduct` - `unpublishProduct` - `updateManyProductsConnection` - `deleteManyProductsConnection` - `publishManyProductsConnection` - `unpublishManyProductsConnection` All of these mutations accept input types that are specific to your projects GraphQL schema. ## Create entries When creating new content entries, the `data` argument will have an associated input type that is specific to your content model. For example, if your project contains the model `Product`, you will have: | Mutation | Argument | Input Type | | --------------- | -------- | --------------------- | | `createProduct` | `data` | `ProductCreateInput!` | ```graphql mutation { createProduct(data: { name: "Face Mask", slug: "face-mask", price: 1000 }) { id name slug price } } ``` ```json { "data": { "createProduct": { "id": "ckgcd5hzc01wd0a446vd3kqrs", "name": "Face Mask", "slug": "face-mask", "price": 1000 } } } ``` The `id` is a [default system field](/docs/api-reference/schema/system-fields#default-model-fields) that is automatically generated for all new entries. ## Update entries When updating single content entry, you must specify the unique `where` criteria of which you want to update, as well as the new `data`. For example, if your project contains the model `Product`, you will have: | Argument | Input Type | | -------- | -------------------------- | | `where` | `ProductWhereUniqueInput!` | | `data` | `ProductUpdateInput!` | ```graphql mutation { updateProduct( where: { id: "ckgcd5hzc01wd0a446vd3kqrs" } data: { price: 100 } ) { id name price } } ``` ```json { "data": { "updateProduct": { "id": "ckgcd5hzc01wd0a446vd3kqrs", "name": "Face Mask", "price": 100 } } } ``` You can also update any unique field on your model. ## Upsert entries The upsert mutation allows you to create, or update a content entry based on whether the unique `where` values exist. For example, if your project contains the model `Product`, you will have: | Argument | Input Type | | ------------------- | -------------------------- | | `where` | `ProductWhereUniqueInput!` | | `upsert` | `ProductUpsertInput!` | | `upsert` > `create` | `ProductCreateInput!` | | `upsert` > `update` | `ProductUpdateInput!` | You must provide both `create`, and `update` to the `upsert` argument. ```graphql mutation { upsertProduct( where: { slug: "face-mask" } upsert: { create: { name: "Face Mask", slug: "face-mask", price: 1000 } update: { name: "Face Mask", slug: "face-mask", price: 1000 } } ) { id name slug price } } ``` ```json { "data": { "upsertProduct": { "id": "ckgcf201401cz0a56wyon4fj8", "name": "Face Mask", "slug": "face-mask", "price": 1000 } } } ``` ## Delete entries Similar to updating, and upserting entries, you can specify using `where` the entries you want to delete. For example, if your project contains the model `Product`, you will have: | Argument | Input Type | | -------- | -------------------------- | | `where` | `ProductWhereUniqueInput!` | ```graphql mutation { deleteProduct(where: { id: "..." }) { id name slug price } } ``` ```json { "data": { "deleteProduct": { "id": "ckgcf201401cz0a56wyon4fj8", "name": "Face Mask", "slug": "face-mask", "price": 1000 } } } ``` ## Nested mutations - `create`: Create and relate entries - `connect`: Connect additional existing entries by unique field - `update`: Update the connected entries - `upsert`: Create or update connected entries - `disconnect`: Disconnect connected relations by unique field - `delete`: Delete all connected entries - `set`: Override all connected entries ### Create ```graphql mutation createOneRelation { createProduct( data: { name: "Test" slug: "test" price: 1000 category: { create: { name: "Accessories", slug: "accessories" } } } ) { id name category { name } } } mutation createManyRelations { createProduct( data: { name: "Test" slug: "test" price: 1000 category: { create: [ { name: "Accessories", slug: "accessories" } { name: "...", slug: "..." } ] } } ) { id name category { name } } } ``` ```graphql mutation createAndConnectOne { createProduct( data: { name: "Test" slug: "test" price: 1000 category: { connect: { slug: "accessories" } } } ) { id name category { name } } } mutation createAndConnectMany { createProduct( data: { name: "Test" slug: "test" price: 1000 categories: { connect: [{ slug: "accessories" }, { id: "..." }] } } ) { id name category { name } } } ``` ### Update ```graphql mutation { updateProduct( where: { id: "..." } data: { category: { create: { name: "Accessories", slug: "accessories" } } } ) { id category { name } } } ``` ```graphql mutation { updateProduct( where: { id: "..." } data: { category: { update: { where: { slug: "accessories" } data: { name: "All Accessories" } } } } ) { id category { name } } } ``` ```graphql mutation { updateProduct( where: { id: "..." } data: { category: { upsert: { where: { slug: "accessories" } data: { create: { name: "Accessories", slug: "accessories" } update: { name: "Accessories", slug: "accessories" } } } } } ) { id category { name } } } ``` ```graphql mutation updateAndConnectOne { updateProduct( where: { id: "..." } data: { category: { connect: { id: "..." } } } ) { id category { name } } } mutation updateAndConnectMany { updateProduct( where: { id: "..." } data: { categories: { connect: [{ where: { id: "..." } }, { where: { id: "..." } }] } } ) { id category { name } } } ``` ```graphql mutation disconnectOneSide { updateProduct( where: { id: "..." } data: { category: { disconnect: true } } ) { id category { name } } } mutation disconnectManySide { updateProduct( where: { id: "..." } data: { categories: { disconnect: [{ id: "..." }, { id: "..." }] } } ) { id category { name } } } ``` ```graphql mutation { updateProduct(where: { id: "..." }, data: { category: { delete: true } }) { id category { name } } } ``` ```graphql mutation { updateCategory( where: { id: "..." } data: { products: { set: [{ slug: "..." }, { id: "..." }] } } ) { name products { name } } } ``` ## Insert at position When inserting related entries, you can `connect` entries at a given position. The position of entries reflects that [fetching relations](/docs/api-reference/content-api/queries#fetching-relations). The `position` input accepts the following values: | Field | Type | Definition | | -------- | --------- | ------------------------------------------------ | | `before` | `ID` | The ID of the entry you want to insert before | | `after` | `ID` | The ID of the entry you want to insert after | | `start` | `Boolean` | Set to `true` if you want to insert at the start | | `end` | `Boolean` | Set to `true` if you want to insert at the end | You must only provide one of these values. #### Before ```graphql mutation { updateAuthor( where: { id: "..." } data: { posts: { connect: { position: { before: "..." } } } } ) { id } } ``` #### After ```graphql mutation { updateAuthor( where: { id: "..." } data: { posts: { connect: { position: { after: "..." } } } } ) { id } } ``` #### Start ```graphql mutation { updateAuthor( where: { id: "..." } data: { posts: { connect: { position: { start: true } } } } ) { id } } ``` #### End ```graphql mutation { updateAuthor( where: { id: "..." } data: { posts: { connect: { position: { end: true } } } } ) { id } } ``` ## Publishing content mutations Hygraph automatically generates publish, and unpublish mutations for each of your content models, including the asset model. Learn more about [publishing and unpublishing content](/docs/api-reference/content-api/content-stages). ## Batch mutations Hygraph supports batch mutations that can be applied to "many" entries at once. You may wish to update, or delete many entries at once that fit given criteria. Batch mutations comply with the [Relay connection type](/docs/api-reference/content-api/queries#fetching-with-relay) specification. ### Update many To update many entries at once, you must use the `updateMany[Model]Connection` mutation. You can use [where](/docs/api-reference/content-api/filtering), or [pagination](/docs/api-reference/content-api/pagination) filters to set the criteria you wish to update. | Argument | Input Type | Description | | -------- | ----------------------- | ----------------------------------------------------------------------------------------------- | | `where` | `ProductManyWhereInput` | [Filtering](/docs/api-reference/content-api/filtering) criteria for entries you want to update. | | `data` | `CreateInput!` | An object that specifies the data you'd like to update matching entries with. | | `first` | `Int` | Seek forwards from end of result set. | | `last` | `Int` | Seek backwards from start of result set. | | `skip` | `Int` | Skip result set by given amount. | | `before` | `ID` | Seek backwards before specific ID. | | `after` | `ID` | Seeks forwards after specific ID. | If you do not pass any filters, then the first 10 entries will be updated. See [Pagination](/docs/api-reference/content-api/pagination) for updating pages. For example, let's update all products where `featured: true`, to be `featured: false`. ```graphql mutation { updateManyProductsConnection( where: { featured: true } data: { featured: false } ) { edges { node { featured } } } } ``` ```json { "data": { "updateManyProductsConnection": { "edges": [ { "node": { "featured": true } }, { "node": { "featured": true } }, { "node": { "featured": true } } ] } } } ``` ### Delete many To delete many entries at once, you must use the `deleteMany[Model]Connection` mutation. You can use [where](/docs/api-reference/content-api/filtering), or [pagination](/docs/api-reference/content-api/pagination) filters to set the criteria you wish to delete. | Argument | Input Type | Description | | -------- | ----------------------- | ----------------------------------------------------------------------------------------------- | | `where` | `ProductManyWhereInput` | [Filtering](/docs/api-reference/content-api/filtering) criteria for entries you want to delete. | | `first` | `Int` | Seek forwards from end of result set. | | `last` | `Int` | Seek backwards from start of result set. | | `skip` | `Int` | Skip result set by given amount. | | `before` | `ID` | Seek backwards before specific ID. | | `after` | `ID` | Seeks forwards after specific ID. | If you do not pass any filters, then the first 10 entries will be deleted. See [Pagination](/docs/api-reference/content-api/pagination) for updating pages. ```graphql mutation { deleteManyProductsConnection(where: { featured: true }) { edges { node { id } } } } ``` ```json { "data": { "deleteManyProductsConnection": { "edges": [ { "node": { "id": "ckdt46o2w029u0156q124e0x4" } }, { "node": { "id": "ckdt47uio02al01044grc4ehf" } }, { "node": { "id": "ckdt4c9gw02cu01026bzkok1b" } } ] } } } ``` ### Publish many Just like you can [publish content](#publishing-content-mutations), you can also batch publish. | Argument | Input Type | Description | | -------- | ------------------------- | ----------------------------------------------------------------------------------------- | | `where` | `ProductManyWhereInput` | [Filtering](/docs/api-reference/content-api/filtering) criteria finding entries. | | `from` | `Stage = DRAFT` | The [content stage](/docs/api-reference/content-api/content-stages) to find entries from. | | `to` | `[Stage!]! = [PUBLISHED]` | The target published [content stage](/docs/api-reference/content-api/content-stages). | | `first` | `Int` | Seek forwards from end of result set. | | `last` | `Int` | Seek backwards from start of result set. | | `skip` | `Int` | Skip result set by given amount. | | `before` | `ID` | Seek backwards before specific ID. | | `after` | `ID` | Seeks forwards after specific ID. | For example, we could publish the first 5 products to the `PUBLISHED` stage. ```graphql mutation { publishManyProductsConnection(first: 5, to: PUBLISHED) { edges { node { id } } } } ``` ```json { "data": { "publishManyProductsConnection": { "edges": [ { "node": { "id": "ckdt46o2w029u0156q124e0x4" } }, { "node": { "id": "ckdt47uio02al01044grc4ehf" } }, { "node": { "id": "ckdt4c9gw02cu01026bzkok1b" } } ] } } } ``` ### Unpublish many Just like you can batch publish, you can also batch unpublish. | Argument | Input Type | Description | | -------- | ------------------------- | --------------------------------------------------------------------------------------------- | | `where` | `ProductManyWhereInput` | [Filtering](/docs/api-reference/content-api/filtering) criteria for entries you want to find. | | `stage` | `Stage = DRAFT` | The [content stage](/docs/api-reference/content-api/content-stages) to find entries in. | | `to` | `[Stage!]! = [PUBLISHED]` | The target published [content stage](/docs/api-reference/content-api/content-stages). | | `first` | `Int` | Seek forwards from end of result set. | | `last` | `Int` | Seek backwards from start of result set. | | `skip` | `Int` | Skip result set by given amount. | | `before` | `ID` | Seek backwards before specific ID. | | `after` | `ID` | Seeks forwards after specific ID. | ```graphql mutation { unpublishManyProductsConnection(stage: PUBLISHED) { edges { node { id } } } } ``` ```json { "data": { "unpublishManyProductsConnection": { "edges": [ { "node": { "id": "ckdt46o2w029u0156q124e0x4" } }, { "node": { "id": "ckdt47uio02al01044grc4ehf" } }, { "node": { "id": "ckdt4c9gw02cu01026bzkok1b" } } ] } } } ``` ## Localized content mutations Depending on whether or not you have localized fields in your schema, you will be able to mutate each of the localized content entries. Learn more about [mutating localized content](/docs/api-reference/content-api/localization#mutating-localized-content). ## Conditional fields When you create a field via mutation, you can use `visibilityCondition` **to set conditional visibility** for it: ```graphql mutation CreateConditionalRichTextField { createSimpleField( data: {type: RICHTEXT, parentId: "6b0ce6afe6c74bd196948b3eb28501a3", apiId: "conditionalRichtext", displayName: "Conditional Richtext", isRequired: false, isUnique: false, isList: false, isLocalized: false, visibilityCondition: {baseField: "d1798ceb369c4aad98d6bd286b879109", operator: IS, booleanValue: true}} ) { migration { id } } } ``` In this example `parentId` is an ID of a model or a component, and `baseField` is an ID of the field used for condition, in this case a boolean field. **To create an entry** for an existing model and set a visibility condition: ```graphql mutation { createSubsidiaries( data: {name: "New location", publicHoliday: true, publicHolidayMessage: "Today is a public holiday!", slug: "new-location", information: "This is a new subsidiary"} ) { id information } } ``` ```json { "data": { "createSubsidiaries": { "id": "cm1kneaip038b07vuy7sdx31w", "information": "This is a new subsidiary" } } } ``` In this example, `Subsidiaries` is a model that contains a `publicHoliday` boolean which, when set to `true`, makes the `publicHolidayMessage` field visible. When you go to the content editor and access the edit view of this content entry, `publicHolidayMessage` will be visible: ![Conditional visibility in the UI](/images/docs/api-reference/content-api/conditional-visibility-ui.png) To set a condition, the model or component needs to have a boolean or enumeration field. Check out our [conditional fields documentation](/docs/developer-guides/schema/conditional-fields) to learn more. --- # Ordering Source: https://hygraph.com/docs/api-reference/content-api/ordering When fetching multiple entries you can use the `orderBy` argument to define the order of the returned records. You can order results by all [System Fields](/docs/api-reference/schema/system-fields), and any non-relational custom field you define in your model, either ascending, or descending. ## Order by types | Input Type | Description | | ------------------ | ---------------------------------------- | | `[fieldName]_ASC` | Order results by `fieldName` ascending. | | `[fieldName]_DESC` | Order results by `fieldName` descending. | ```graphql { posts(orderBy: createdAt_ASC) { id createdAt } } ``` ```graphql { posts(orderBy: createdAt_DESC) { id createdAt } } ``` You can only use **one** `inputType` with `orderBy` to order results. ## Nested ordering You can use the `orderBy` with [two-way references](/docs/api-reference/schema/field-types#two-way-references). [One-way references](/docs/api-reference/schema/field-types#one-way-references) and [union-type relations](/docs/api-reference/schema/field-types#union) are not supported. For example, let's imagine our post has a `authors` relation. The same `orderBy` rules apply. ```graphql { posts { id createdAt relatedPosts(orderBy: createdAt_DESC) { id createdAt } } } ``` --- # Pagination Source: https://hygraph.com/docs/api-reference/content-api/pagination ## Paginate query results Hygraph supports various arguments for paginating content entries: | Argument | Type | Definition | | -------- | -------- | --------------------------------------- | | `first` | `Int` | Seek forwards from start of result set. | | `last` | `Int` | Seek backwards from end of result set. | | `skip` | `Int` | Skip result set by given amount. | | `before` | `String` | Seek backwards before specific ID. | | `after` | `String` | Seeks forwards after specific ID. | ```graphql { posts(first: 6) { id } } ``` ```graphql { posts(last: 6) { id } } ``` ```graphql { posts(first: 6, skip: 6) { id } } ``` ```graphql { posts(last: 6, before: "...") { id } } ``` ```graphql { posts(first: 6, after: "...") { id } } ``` You cannot combine `first` with `before`, or `last` with `after`. The default result size of results returned by queries fetching multiple entries is `10`. You can provide a maximum of `100` to the `first`, or `last` arguments. The limit of 10/100 applies to projects created after 14-06-2022. Projects created before that date have a limit of 100/1000. ## Pagination limits To fetch the pagination limits of your projects, you need to access the [API Playground](/docs/api-reference/basics/api-playground) section of your Hygraph project, select `Management API` from the `Environment` dropdown located at the top of the screen, and run the following query: ```graphql { viewer { project(id: "") { defaultPaginationSize maxPaginationSize } } } ``` ## Nested pagination You can also use `first`, `last`, `skip`, `before`, and `after` arguments with any nested relations. For example, let's imagine our post has comments: ```graphql { posts { id comments(first: 6, skip: 6) { id createdAt } } } ``` ## Relay cursor connections Hygraph follows the Relay cursor connection specification. Each of your project models also contain a connection type, automatically managed by Hygraph. The example below shows us how we can query for the first `3` posts, `after` the `cursor` (ID) `abc`. We can also query `pageInfo` to check whether there are more pages using `hasNextPage`. The [`PageInfo`](/docs/api-reference/schema/system-fields#pageinfo) is useful when paginating. ```graphql { postsConnection(first: 3, after: "abc") { edges { cursor node { id title } } pageInfo { hasNextPage hasPreviousPage startCursor endCursor pageSize } } } ``` Learn more about [fetching with Relay](/docs/api-reference/content-api/queries#fetching-with-relay). --- # Queries Source: https://hygraph.com/docs/api-reference/content-api/queries ## Overview Hygraph automatically generates queries for fetching single, and multiple entries for each defined content type belonging to your project. You will need a [Permanent Auth Token](/docs/api-reference/basics/authorization#permanent-auth-tokens), or your [Public API Permissions](/docs/api-reference/basics/authorization#public-api-permissions) configured to query any data from your project. ## Auto-generated queries When a new model is added to your project, there are two generated GraphQL queries added to your schema. The queries are named after the `API ID`, and `Plural API ID`. Both the single, and plural queries come with their own generated arguments for filtering, ordering, paginated, and getting content via their stage, revision, or locale. For example, let's assume we have the model `Post` in our schema, and opted to keep the default generated API ID, and Plural API ID. The following queries would be generated by the API automatically: - `post` - `posts` - `postVersion` - `postsConnection` ## Fetching a single entry The `post` query is what you would use to fetch one entry from the CMS. You can fetch an individual entry by `id`, or any unique non-localized field defined in your content type. ```graphql { post(where: { id: "..." }) { id title } } ``` ## Fetching multiple entries The `posts` query is what you should use to fetch multiple entries from the CMS. ```graphql { posts { id } } ``` ## Fetching relations Imagine `posts` have a [one to many](/docs/api-reference/schema/field-types#one-to-many) relation with comments. With GraphQL you can query the related `comments` in the same request. Here we will get all posts, and their comments. ```graphql { posts { id comments { id author } } } ``` Learn more about [Relations](/docs/api-reference/schema/field-types#reference). ## Fetching localizations When fetching one or more entry, you can also fetch the localized entries. The default locale is set to `en`. ```graphql { post(where: { id: "..." }, locales: [en, fr, de]) { title } posts(locales: [en, fr, de]) { title } } ``` Learn more about [Localization](/docs/api-reference/content-api/localization). ### Locales inside components When Localized fields only exist inside components, querying for content can be a bit misleading, especially when querying for locales. For instance, to query for the “Russian” Locale inside the components, specifying the locale in the query like the example below, will return null: ```graphql query MyQuery { pages(locales: ru) { id title slug pageElements { ... on Header { title contacts } ... on Footer { title texts } } } } ``` ```json { "data": { "pages": [] } } ``` The reason for this is that the parent entry - Page - does not exist in the requested locale. Instead, asking for the "German" locale will return results like the one shown below, as this is the default - Base - locale, and entries will always exist in the default locale: ```graphql query MyQuery { pages(locales: de) { id title slug pageElements { ... on Header { title contacts } ... on Footer { title texts } } } } ``` ```json { "data": { "pages": [ { "id": "cl793mb8unoao0buuop235856", "title": "Pricing Page", "slug": "pricing", "pageElements": [ { "title": "Subscription", "contacts": 430 }, { "title": "Contact", "texts": ["text 1", "text 2"] } ] } ] } } ``` The Page model - parent entry in this example - has two fields, **Title** and **Slug**, that are not localized, as well as components. In turn, the components - children - have localized fields inside. So, when you create an entry, only the components - children - have localization and not the parent entry. This means that, when you query for pages in a locale that is not the default and the parent entry does not exist in that locale, the query will return null. There are three workarounds to query locales inside components: - [Specify the locale for the component](/docs/api-reference/content-api/queries#specify-locale-for-the-component) - [Use localizations inside the component in your query](/docs/api-reference/content-api/queries#use-localizations-inside-the-component-in-your-query) - [Add a localized field to the parent entry](/docs/api-reference/content-api/queries#add-a-localized-field-to-the-parent-entry) Here are some examples of these workarounds, following the German/Russian locales example we used before: #### Specify locale for the component To query for the Russian locale in our example, you simply need to specify the locale for the component, as shown below: ```graphql query MyQuery { pages { id title slug pageElements(locales: ru) { ... on Header { title contacts } } } } ``` ```json { "data": { "pages": [ { "id": "cl7g3mb8unoao0buuop235856", "title": "Pricing Page", "slug": "pricing", "pageElements": [ { "title": "Подписка", "contacts": 320 } ] } ] } } ``` #### Use localizations inside the component in your query Another way to query for all the locales inside the component is to use **localizations** inside the component in your query, as follows: ```graphql query MyQuery { pages { id title slug pageElements { ... on Header { id localizations(includeCurrent: true, locales: [de, ru]) { title contacts locale } } ... on Footer { title texts } } } } ``` ```json { "data": { "pages": [ { "id": "cl7g3mb8unoao0buuop23586", "title": "Pricing Page", "slug": "pricing", "pageElements": [ { "id": "cl7g3mb8unoap0buuwzpoipja", "localizations": [ { "title": "Subscription", "contacts": 430, "locale": "de" }, { "title": "Подписка", "contacts": 320, "locale": "ru" } ] }, { "title": "Contact", "texts": ["text 1", "text 2"] } ] } ] } } ``` #### Add a localized field to the parent entry The third and final workaround would be to add a localized field to the parent entry, as shown in the example below: ![Add a localized field to the parent entry](/images/docs/api-reference/content-api/locales-in-components-3rdworkaround-a.png) This makes it possible to request the locale at the beginning of the query. ```graphql query MyQuery { pages(locales: ru) { id title slug pageElements { ... on Header { localizations(includeCurrent: true, locales: [de, ru]) { title contacts locale } } ... on Footer { title texts } } } } ``` ```json { "data": { "pages": [ { "id": "cl7g3mb8unoao0buuop23586", "title": "Pricing Page", "slug": "adding-russian-locale", "pageElements": [ { "localizations": [ { "title": "Subscription", "contacts": 430, "locale": "de" }, { "title": "Подписка", "contacts": 320, "locale": "ru" } ] }, { "title": "Contact", "texts": ["text 1", "text 2"] } ] } ] } } ``` ## Fetching stages When fetching entries, you can also specify the content `stage`. The default content stage is set to `DRAFT`. ```graphql { post(where: { id: "..." }, stage: PUBLISHED) { title } posts(stage: PUBLISHED) { title } } ``` Learn more about [Content Stages](/docs/api-reference/content-api/content-stages). ## Fetching versions You can fetch all data of a specific entry at a point in time using the automatically generated version query. For example, using the `postVersion` query from above, we can make a request to get the specific revision through a query: ```graphql { postVersion(where: { id: "abc123", revision: 1, stage: PUBLISHED }) { id revision data } } ``` Learn more about [Versioning](/docs/api-reference/content-api/content-stages#versioning). ## Fetching workflow step You can fetch the workflow step for an entry. For example, for the model `Post` with workflow enabled, we can make a request to get the workflow step for an entry: ```graphql { posts { id workflowStep } } ``` Learn more about [Content workflows](/docs/developer-guides/project/content-workflows). ## Combining arguments It is also possible to pass more than one query argument at a time. For example, here we can get the first 3 posts, ordered by the created timestamp, where the title contains "Hygraph", and is published. ```graphql { posts( where: { title_contains: "Hygraph" } orderBy: createdAt_DESC first: 3 stage: PUBLISHED ) { id } } ``` Learn more about these query arguments: - [Filtering](/docs/api-reference/content-api/filtering) - [Ordering](/docs/api-reference/content-api/ordering) - [Pagination](/docs/api-reference/content-api/pagination) - [Content Stage](/docs/api-reference/content-api/content-stages) ## Combining queries Multiple queries can be executed in parallel via a single request to your endpoint. For example, let's fetch our a single, and multiple posts in one request. ```graphql { post(where: { id: "..." }) { id title } posts { id title } } ``` For example, here we query for all events, and alias a second query with `previous` to better represent the applied filter. ```graphql { events(where: { start_gt: "2020-10-07T09:00:00+00:00" }) { start } previous: events(where: { start_lt: "2020-10-07T09:00:00+00:00" }) { start } } ``` ## Fetching with Relay Hygraph also implements the Relay specification for querying records for all projects. You can fetch a single entry using the `node` query, or multiple entries with the `postsConnection` type. When fetching a single entry with `node`, you will need to also pass the Edge Type inside the query. ### Node ```graphql { node(id: "...") { ... on Post { id title } } } ``` You can use the generated Relay connection query for querying multiple entries. ### Connection / Edges ```graphql { postsConnection { edges { cursor node { id title } } } } ``` Learn more about the [system fields](/docs/api-reference/schema/system-fields#connection-type-fields) for connection type queries. ## Directives We support both the `@skip` and `@include` directives for use in your schema. This allows you to skip or include a field based on the value of the `if` argument you provide. ### @skip For example, below can use the `@skip` directive to skip including a field based on the `skipTitle` variable value. ```graphql query ($skipTitle: Boolean!) { posts { id title @skip(if: $skipTitle) } } ``` ```json { "skipTitle": true } ``` ### @include For example, below can use the `@include` directive to include a field (including relations) based on the `includeAuthor` variable value. ```graphql query ($includeAuthor: Boolean!) { posts { id title author @include(if: $includeAuthor) { name } } } ``` ```json { "includeAuthor": true } ``` ## Variables It's recommended you use GraphQL variables when working with queries that use any variable data values. This is useful for reusing queries across your application. For example, to fetch a post by slug, you'd first need to define the query name, and the arguments with the type, and pass that along to the query itself. ```graphql query GetPostBySlug($slug: String!) { post(where: { slug: $slug }) { id title } } ``` When working with a GraphQL client, this is how you'd typically work with variables: ```js import { request, gql } from 'graphql-request'; const endpoint = 'YOUR_HYGRAPH_ENDPOINT'; const query = gql` query GetPostBySlug($slug: String!) { post(where: { slug: $slug }) { id title } } `; const variables = { slug: 'hygraph-is-awesome', }; const data = await request(endpoint, query, variables); ``` ```js import { gql, useQuery } from '@apollo/client'; const query = gql` query GetPostBySlug($slug: String!) { post(where: { slug: $slug }) { id title } } `; function PostPage({ breed }) { const { loading, error, data } = useQuery(query, { variables: { slug: 'hygraph-is-awesome' }, }); if (loading) return null; if (error) return `Error! ${error}`; return
{JSON.stringify(data, null, 2)}
; } ```
--- # Rich Text field Source: https://hygraph.com/docs/api-reference/content-api/rich-text-field ## Overview The `RichText` field type is an advanced String field that returns your content in 4 different formats by default: `raw`, `HTML`, `markdown`, and `text`. `JSON` is also available when embeds are enabled. The Rich Text field renders an advanced `textarea` with tools to add headings, links, tables, images, lists, etc. When a Rich Text field is added to your model, it will automatically generate the following types: ```graphql type RichText { raw: RichTextAST! html: String! markdown: String! text: String! json: RichTextAST! } ``` ## Rich Text data Let's talk about Rich Text data in more detail. - **Raw:** `raw` AST (Slate Nodes) offers complete control over how nodes are presented to the user. - **JSON:** `JSON` representations of RTE allows as much control as `raw` does, but offers the possibility of creating embeds. `RichText` will include the field `JSON` in addition to `raw` only if Rich Text Embeds are enabled. Both `JSON` and `raw` are aliases, but if you have embeds enabled you should ideally use `JSON`. - **HTML:** `HTML` can be used with a Rich Text renderer for customization purposes. While rendering the HTML of your rich text is simple, it doesn't offer great customization. - **Markdown:** `Markdown` - like `HTML` - can be used for customization. While `markdown` is easier to read and write - specially for users without a technical background - it's not as expressive as `HTML`. To present `markdown` on a page, you'll need a `markdown` parser that will convert `markdown` to `HTML`. - **Text:** `text` is mostly used for excerpts, as links, images, and even line breaks are removed. ### Use Cases - **Raw:** Use `raw` when you want to control the rendered output using our render libraries. - **JSON:** Use `JSON` for the same as `raw` but when you also need embeds. - **HTML / Markdown:** Use these when you need to make customizations and want to insert pre-built `HTML` or `markdown` into an app. - **Text:** Use `text` when you want to provide the text as data to a script, such as making full-text search. ### Examples This section takes a simple Rich Text piece with a title, a paragraph, a link, and bold text, and offers its `JSON`, `HTML`, `Markdown`, and `Text` representations. ``` "json": { "children": [ { "type": "heading-one", "children": [ { "text": "Example Rich Text Copy" } ] }, { "type": "paragraph", "children": [ { "text": "This is a regular paragraph, including a " }, { "href": "https://hygraph.com/docs", "type": "link", "children": [ { "text": "hyperlink" } ] }, { "text": " and " }, { "bold": true, "text": "bold text" }, { "text": "." } ] } ] } ``` ``` "html": "

Example Rich Text Copy

This is a regular paragraph, including a hyperlink and bold text.

" ```
```markdown "markdown": "# Example Rich Text Copy\n\nThis is a regular paragraph, including a [hyperlink](https://hygraph.com/docs \"https://hygraph.com/docs\") and **bold text**.\n" ``` ```text "text": "Example Rich Text Copy\\nThis is a regular paragraph, including a \\nhyperlink\\n and \\nbold text\\n." ```
## Rich Text embeds If Rich Text Embeds are enabled, `RichText` will include the field `JSON` in addition to `raw`. For example, we can query all of those on our `RichText` field type `content`: ```graphql { posts { content { raw html markdown text } } } ``` ```json { "data": { "posts": [ { "content": { "raw": { "children": [ { "type": "paragraph", "children": [ { "text": "GraphQL CMS" } ] } ] }, "html": "

GraphQL CMS

", "markdown": "GraphQL CMS\n", "text": "GraphQL CMS" } } ] } } ```
### Embed assets You can also embed [Assets](/docs/api-reference/assets/embedded-types) and other models inside Rich Text as **block, inline or link embeds**. ![Rich Text Asset Embeds UI](/images/docs/api-reference/schema/rich-text-embeds.png) You can find out how to enable Rich Text embeds in our [field configuration](/docs/api-reference/schema/field-configuration#rich-text-fields-only-embed-options) docs. ### Rich Text embeds & API types With Rich Text Embeds enabled, your API will have some new types added. The name of your field will be now a type appended by `RichText`, and `RichTextEmbeddedTypes` inside your schema. For example, if you had the model `Post` and field `content`, the types generates would be `PostContentRichText`, and `PostContentRichTextEmbeddedTypes` respectively. The `PostContentRichText` type will look like the following: ```graphql type RichText { json: RichTextAST! html: String! markdown: String! text: String! references: [PostContentRichTextEmbeddedTypes!]! } ``` The `references` field will be a union relation to the types you embedded, for example `Asset`. You should use the `references` field when querying `JSON` to get the `URL` (with any [transformations](/docs/api-reference/assets/transformations), `handle`, or any of the [Asset fields](/docs/api-reference/schema/system-fields#asset-fields). ```graphql { posts { content { json html markdown text references { __typename ... on Asset { url handle } } } } } ``` ```json { "data": { "posts": [ { "content": { "json": { "children": [ { "type": "paragraph", "children": [ { "text": "Hygraph Rich Text Embeds" } ] }, { "type": "embed", "nodeId": "cko2lq2u0031r0844xnvurz05", "children": [ { "text": "" } ], "nodeType": "Asset" }, { "type": "paragraph", "children": [ { "text": "" } ] } ] }, "html": "

Hygraph Rich Text Embeds

", "markdown": "Hygraph Rich Text Embeds\n\n\n", "text": "Hygraph Rich Text Embeds\\n\\n", "references": [ { "__typename": "Asset", "url": "https://media.graphassets.com/xSIoGkATQybd8S2SgA5Q", "handle": "xSIoGkATQybd8S2SgA5Q" } ] } } ] } } ```
The `HTML` response will return `gcms-embed-type` and `gcms-embed-id` data attributes for the embedded types. A block embed is returned as `div` and an inline embed as `span` with a `data-gcms-embed-inline` attribute. A link embed is returned as an `a`-tag with a `data-gcms-embed-id` and `data-gcms-embed-type` attribute. ```html
```
```html ``` ```html link text ```
Hygraph uses Slate 0.5 for `RichTextAST`. If you are programmatically creating content entries with Rich Text, you should use the @graphcms/html-to-slate-ast package. ## Mutations for Rich Text fields When creating or updating entries, ensure your mutation payload matches the schema type shown in the API Playground. For localized models or fields, you must include a locale property. Incorrectly structured payloads are a common cause of Invalid payload / could not transform RichText errors. Even if the field allows embeds, the structure remains the same. Always send an object containing a `children` array. ### Create rich text fields Provide the AST object directly. Do not wrap it in a property such as `raw` or `json`. ```graphql mutation { createPost( data: { content: { children: [{type: "paragraph", children: [{text: "This is old."}]}] } } ) { id content { json html } } } ``` ```json { "data": { "createPost": { "id": "cmgqebp1hxdqm07w0fgaj7u7g", "content": { "json": { "children": [ { "type": "paragraph", "children": [ { "text": "This is old." } ] } ] }, "html": "

This is old.

" } } } } ```
### Update rich text fields Provide the AST object directly. Do not wrap it in a property such as `raw` or `json`. ```graphql mutation { updatePost( where: {id: "cmgqe9mbzxqx607uu4uzrde44"} data: { content: { children: [{ type: "paragraph", children: [{ text: "This is new." }] }] } } ) { id content { json } } } ``` ```json { "data": { "updatePost": { "id": "cmgqe9mbzxqx607uu4uzrde44", "content": { "json": { "children": [ { "type": "paragraph", "children": [ { "text": "This is new." } ] } ] } } } } } ``` ### Create rich text fields with locale Provide the AST object directly. Do not wrap it in a property such as `raw` or `json`. ```graphql mutation { createPost( data: { localizations: { create: [ { locale: de data: { content: { children: [ { type: "paragraph", children: [{ text: "Das ist alt." }] } ] } } } ] } } ) { id localizations(locales: [de]) { locale content { raw } } } } ``` ```json { "data": { "createPost": { "id": "cmgqem7eoym1d07uu2ezhlj5t", "localizations": [ { "locale": "de", "content": { "raw": { "children": [ { "type": "paragraph", "children": [ { "text": "Das ist alt." } ] } ] } } } ] } } } ``` ### Update rich text fields with locale Provide the AST object directly. Do not wrap it in a property such as `raw` or `json`. ```graphql mutation { updatePost( where: { id: "cmgqem7eoym1d07uu2ezhlj5t" } data: { localizations: { update: [ { locale: de data: { content: { children: [ { type: "paragraph", children: [{ text: "Das ist neu." }] } ] } } } ] } } ) { id localizations(locales: [de]) { locale content { raw } } } } ``` ```json { "data": { "updatePost": { "id": "cmgqem7eoym1d07uu2ezhlj5t", "localizations": [ { "locale": "de", "content": { "raw": { "children": [ { "type": "paragraph", "children": [ { "text": "Das ist neu." } ] } ] } } } ] } } } ``` ## Use JSON representation of RTE for customization You can work with the **Rich Text field** to take the data that the editors put in Hygraph, and manipulate it for display in the front end. The following example shows data available on a blog post, with the Rich Text content in `HTML` and `markdown`: ```graphql query MyQuery($id: ID, $slug: String) { values: post(where: { id: $id, slug: $slug }) { title slug id content { html markdown } } } ``` ```json { "id": "clbdn2fjithnl0amxwm8wtell" } ``` ```json { "data": { "values": { "title": "Test", "slug": "test", "id": "clbdn2fjithnl0amxwm8wtell", "content": { "html": "

Lorem Ipsum

"Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..."

"There is no one who loves pain itself, who seeks after it and wants to have it, simply because it is pain..."

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc interdum mauris libero. Aliquam in iaculis nibh. Duis sagittis, orci sit amet hendrerit pellentesque, velit felis lobortis purus, vitae finibus libero risus quis ipsum. Curabitur in malesuada odio. Quisque quis metus quis augue lacinia euismod vel quis orci. Fusce condimentum ultricies mollis. Morbi et ultricies augue. Integer massa libero, elementum nec ante at, blandit auctor risus. Nulla et dignissim turpis. Vivamus pharetra turpis eu sem maximus egestas. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In a pulvinar dui. Etiam varius justo quis purus hendrerit, sed tristique ex pulvinar. Integer vel lobortis magna. Suspendisse potenti. Morbi faucibus sem eget enim posuere, vitae vestibulum felis sodales.

Fusce suscipit sed dolor eu convallis. Nulla et velit ut lacus ullamcorper varius vel sit amet justo. Nullam condimentum dolor a ligula placerat, vitae sagittis elit efficitur. Donec ac elit fermentum, vehicula dui eget, condimentum ante. Aenean in nisl faucibus neque feugiat suscipit. Aenean sit amet facilisis ante, sed semper ipsum. Proin id nulla odio. Maecenas condimentum placerat laoreet. Aliquam at sodales tortor. Nam tortor dui, maximus nec imperdiet ut, bibendum vel felis. Duis hendrerit vulputate finibus. In hac habitasse platea dictumst. Pellentesque sed posuere ex. Nullam metus erat, finibus nec ultricies nec, egestas sed ipsum.

Nulla varius mauris sed justo egestas, eu dictum urna dictum. Aenean varius dui convallis vehicula suscipit. In hac habitasse platea dictumst. Nullam euismod vel dui ac blandit. Suspendisse potenti. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed vitae lorem lacinia, mollis risus volutpat, malesuada risus. Maecenas lacinia sem in odio vestibulum, at dictum metus finibus. Morbi turpis tellus, egestas eu molestie nec, sagittis ac enim. Morbi facilisis finibus odio, eget consequat ipsum porttitor non. Nunc risus ipsum, congue blandit lacus eu, lacinia hendrerit nulla. Donec congue suscipit velit, a aliquet dolor venenatis nec. In eget ullamcorper massa. Ut efficitur felis vel rhoncus sollicitudin.

", "markdown": "# Lorem Ipsum\n\n#### \"Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit...\"\n\n##### \"There is no one who loves pain itself, who seeks after it and wants to have it, simply because it is pain...\"\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc interdum mauris libero. Aliquam in iaculis nibh. Duis sagittis, orci sit amet hendrerit pellentesque, velit felis lobortis purus, vitae finibus libero risus quis ipsum. Curabitur in malesuada odio. Quisque quis metus quis augue lacinia euismod vel quis orci. Fusce condimentum ultricies mollis. Morbi et ultricies augue. Integer massa libero, elementum nec ante at, blandit auctor risus. Nulla et dignissim turpis. Vivamus pharetra turpis eu sem maximus egestas. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In a pulvinar dui. Etiam varius justo quis purus hendrerit, sed tristique ex pulvinar. Integer vel lobortis magna. Suspendisse potenti. Morbi faucibus sem eget enim posuere, vitae vestibulum felis sodales.\n\nFusce suscipit sed dolor eu convallis. Nulla et velit ut lacus ullamcorper varius vel sit amet justo. Nullam condimentum dolor a ligula placerat, vitae sagittis elit efficitur. Donec ac elit fermentum, vehicula dui eget, condimentum ante. Aenean in nisl faucibus neque feugiat suscipit. Aenean sit amet facilisis ante, sed semper ipsum. Proin id nulla odio. Maecenas condimentum placerat laoreet. Aliquam at sodales tortor. Nam tortor dui, maximus nec imperdiet ut, bibendum vel felis. Duis hendrerit vulputate finibus. In hac habitasse platea dictumst. Pellentesque sed posuere ex. Nullam metus erat, finibus nec ultricies nec, egestas sed ipsum.\n\nNulla varius mauris sed justo egestas, eu dictum urna dictum. Aenean varius dui convallis vehicula suscipit. In hac habitasse platea dictumst. Nullam euismod vel dui ac blandit. Suspendisse potenti. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed vitae lorem lacinia, mollis risus volutpat, malesuada risus. Maecenas lacinia sem in odio vestibulum, at dictum metus finibus. Morbi turpis tellus, egestas eu molestie nec, sagittis ac enim. Morbi facilisis finibus odio, eget consequat ipsum porttitor non. Nunc risus ipsum, congue blandit lacus eu, lacinia hendrerit nulla. Donec congue suscipit velit, a aliquet dolor venenatis nec. In eget ullamcorper massa. Ut efficitur felis vel rhoncus sollicitudin.\n" } } } } ```
Hygraph automatically serializes the content into `HTML` and/or `markdown` that the front end can simply display. **This does not allow customization**. Instead of these two things, you can get the JSON representation, which will display as `JSON AST` in a tree with nested levels. ```graphql query MyQuery($id: ID, $slug: String) { values: post(where: { id: $id, slug: $slug }) { title slug id content { json } } } ``` ```json { "id": "clbdn2fjithnl0amxwm8wtell" } ``` ```json { "data": { "values": { "title": "Test", "slug": "test", "id": "clbdn2fjithnl0amxwm8wtell", "content": { "json": { "children": [ { "type": "heading-one", "children": [ { "text": "Lorem Ipsum" } ] }, { "type": "heading-four", "children": [ { "text": "\"Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit...\"" } ] }, { "type": "heading-five", "children": [ { "text": "\"There is no one who loves pain itself, who seeks after it and wants to have it, simply because it is pain...\"" } ] }, { "type": "paragraph", "children": [ { "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc interdum mauris libero. Aliquam in iaculis nibh. Duis sagittis, orci sit amet hendrerit pellentesque, velit felis lobortis purus, vitae finibus libero risus quis ipsum. Curabitur in malesuada odio. Quisque quis metus quis augue lacinia euismod vel quis orci. Fusce condimentum ultricies mollis. Morbi et ultricies augue. Integer massa libero, elementum nec ante at, blandit auctor risus. Nulla et dignissim turpis. Vivamus pharetra turpis eu sem maximus egestas. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In a pulvinar dui. Etiam varius justo quis purus hendrerit, sed tristique ex pulvinar. Integer vel lobortis magna. Suspendisse potenti. Morbi faucibus sem eget enim posuere, vitae vestibulum felis sodales." } ] }, { "type": "paragraph", "children": [ { "text": "Fusce suscipit sed dolor eu convallis. Nulla et velit ut lacus ullamcorper varius vel sit amet justo. Nullam condimentum dolor a ligula placerat, vitae sagittis elit efficitur. Donec ac elit fermentum, vehicula dui eget, condimentum ante. Aenean in nisl faucibus neque feugiat suscipit. Aenean sit amet facilisis ante, sed semper ipsum. Proin id nulla odio. Maecenas condimentum placerat laoreet. Aliquam at sodales tortor. Nam tortor dui, maximus nec imperdiet ut, bibendum vel felis. Duis hendrerit vulputate finibus. In hac habitasse platea dictumst. Pellentesque sed posuere ex. Nullam metus erat, finibus nec ultricies nec, egestas sed ipsum." } ] }, { "type": "paragraph", "children": [ { "text": "Nulla varius mauris sed justo egestas, eu dictum urna dictum. Aenean varius dui convallis vehicula suscipit. In hac habitasse platea dictumst. Nullam euismod vel dui ac blandit. Suspendisse potenti. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed vitae lorem lacinia, mollis risus volutpat, malesuada risus. Maecenas lacinia sem in odio vestibulum, at dictum metus finibus. Morbi turpis tellus, egestas eu molestie nec, sagittis ac enim. Morbi facilisis finibus odio, eget consequat ipsum porttitor non. Nunc risus ipsum, congue blandit lacus eu, lacinia hendrerit nulla. Donec congue suscipit velit, a aliquet dolor venenatis nec. In eget ullamcorper massa. Ut efficitur felis vel rhoncus sollicitudin." } ] } ] } } } } } ``` Remember that `Richtext` will only include the field `JSON` if Rich Text embeds are enabled for the model you're using. As you can see in the `results` tab of the above query, this breaks up the initial data into a `JSON` representation that a renderer can understand. This allows you to take that data and manipulate it in order to override any default renderer or add renderers for custom elements, creating a custom display logic for your front end. You will do this by creating an `HTML` element containing the manipulated data, which will then be rendered via the `astToHtmlString` method that's available on our Rich Text HTML renderer. We also have a React version of this. Click here to access a detailed example on how to style Rich Text using TailwindCSS. By styling your Rich Text fields, you can either customize how your Rich Text will display throughout your website, or even have multiple types of Rich Text fields that do different things. ## Resources - [Hygraph's Rich Text editor:](/docs/developer-guides/content/rich-text-editor) Hygraph's UI Rich Text field feature walkthrough. - Styling Rich Text with TailwindCSS: Detailed tutorial on how to use the `JSON` representation from the RTE to create custom elements for each text-based element of Rich Text. - Introducing the Hygraph React Rich Text Renderer: Blog post on how to render Hygraph documents using Rich Text in your application easily using our available packages. - [Rich Text editor UI guide:](/docs/developer-guides/content/rich-text-editor) Guide on how to use Hygraph's Rich Text editor in the content editor of your project. --- # Variables Source: https://hygraph.com/docs/api-reference/content-api/variables ## Overview A **GraphQL** request is made up of two parts, one containing the query or mutation, and another - declared after it - containing variables. Variables can be used to create dynamic queries and mutations, as they allow you to pass dynamic values as a separate dictionary. In other words, variables in GraphQL are passed like arguments to a function allowing said arguments to be extracted as variables from queries and mutations, simplifying them. ## Variable definitions Variable definitions list all the variables starting with the `$` symbol, followed by the argument type. They can be optional or required. Required variable definitions carry an `!` next to the type. So, `($slug: String!)` defines a variable with name slug, of type String, that is required. If the field you're passing the variable into requires a non-null argument, you need to make the variable required as well. If you want to define more than one variable, you need to write one next to the other in the query. You can separate them with a comma, but it's not necessary. Here's an example that fetches posts that have either the `title` or `slug` provided in the query variables: ```graphql query name($title: String, $slug: String) { posts(where: { OR: [{ title: $title }, { slug: $slug }] }) { id title slug } } ``` ```json { "title": "test", "slug": "test" } ``` ```json { "data": { "posts": [ { "id": "clbdn2fjithnl0amxwm8wtell", "title": "Test", "slug": "test" } ] } } ``` ### Define a default When you define a variable, you can also define the default that it will fall back to when you're not passing a value. To assign a default value to a variable in the query, add it after the type declaration, as follows: ```graphql query GetPostBySlug($slug: String = "test") { post(where: { slug: $slug }) { id title } } ``` ```json { "data": { "post": { "id": "clbdn2fjithnl0amxwm8wtell", "title": "Test" } } } ``` In the above example, we set the string `test` as the default for `$slug`. So, if we're not passing any variable values, it uses its default and returns posts where the `slug` is `test`. ## Input types If you variabilize filters or mutations, you need to use the correct input types. The auto generated documentation in our API Playground contains this information: Hover over your query parameters and: - CMD + Click on Mac. - Control + Click on Windows. This opens the documentation explorer, allowing you to look through the API and find the correct input type you need to use. The documentation explorer will also show you the parameters you can pass in your query. ## Queries The following example query fetches a post by slug. In order to do this we have defined the query name and the arguments with the type, and passed that along to the query itself. ```graphql query GetPostBySlug($slug: String!) { post(where: { slug: $slug }) { id title } } ``` ```json { "slug": "title-slug" } ``` ```json { "data": { "post": { "id": "clb5blvzekcnh0altbyxx220a", "title": "Title slug" } } } ``` ## Filters You can variabilize the filtering of your query, making it more flexible. The following query contains dynamic filters with values you can define with the variables you pass: ```graphql query name($where: PostWhereInput) { posts(where: $where) { id title slug } } ``` ```json { "where": { "slug": "test" } } ``` ```json { "data": { "posts": [ { "id": "clbdn2fjithnl0amxwm8wtell", "title": "Test", "slug": "test" } ] } } ``` This way your query can stay the same and instead of creating a new query from scratch every time, you can simply change the values passed with the variables. ## Mutations Just like with filters, if you variabilize mutations, you don't need to write a static mutation every time. Instead, you will keep the same query and only alter the variables. ```graphql mutation name($data: PostCreateInput!) { createPost(data: $data) { id } } ``` ```json { "data": { "title": "post", "slug": "post" } } ``` ```json { "data": { "createPost": { "id": "clbdo25sbtqei0bn1tuovkf8t" } } } ``` --- # API Reference Source: https://hygraph.com/docs/api-reference/index Our API reference contains information on how to work with our Content API, as well as our Schema, Management SDK, and other developer tools. This section of our documentation covers: ## Basics | Document | Contents | | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Authorization](/docs/api-reference/basics/authorization) | Contains information about Public API permissions, Permanent auth tokens, and API endpoints | | [Permissions](/docs/api-reference/basics/permissions) | Contains information about our advanced permissions feature, which allows you to configure granular permissions to access content from a Hygraph project. | | [Caching](/docs/api-reference/basics/caching) | Contains information about cache management through two different content API endpoints: the regular read & write endpoint, and the high performance read-only endpoint. | | [Environments](/docs/api-reference/basics/environments) | Contains information about working with different environments. | | [Errors](/docs/api-reference/basics/errors) | Contains information about common error codes. | | [Webhooks](/docs/api-reference/basics/webhooks) | Contains information about working with webhooks in your Hygraph project. | | [API Playground](/docs/api-reference/basics/api-playground) | Contains information about Hygraph's API Playground, which is a great way to experiment with querying, and mutating data. | | [Rate limits](/docs/api-reference/basics/api-limits) | Contains information about the limits to the number of uncached requests you can make to your content API. Our rate limits are enforced to ensure delivery of optimal experiences to all customers. | ## Schema | Document | Contents | | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Models](/docs/api-reference/schema/models) | Contains information about working with models. The models you create and the fields you add define your project's schema. | | [System fields](/docs/api-reference/schema/system-fields) | Contains information about system fields. All default and custom content types come with some managed system fields. | | [Field types](/docs/api-reference/schema/field-types) | Contains information about the field types you can use in your project. It also contains information about how input types work for filtering, ordering, paginating, and mutating data. | | [Field configuration](/docs/api-reference/schema/field-configuration) | Contains information on how to configure field types for your model with validations, visibility options, and more. | | [Enumerations](/docs/api-reference/schema/enumerations) | Contains information on how to work with enumerations. | | [Taxonomies](/docs/api-reference/schema/taxonomies) | Contains information on how to work with taxonomies. | [Components](/docs/developer-guides/schema/components) | Contains information on how to work with components. A component is a predefined set of fields that can be reused across models and content entries. | | [Reserved terms](/docs/developer-guides/schema/reserved-terms) | This document contains a list of reserved terms, organized by category. Attempting to use these terms will result in a warning, informing you that you must use a different word instead. | | [Environment diffing](/docs/api-reference/schema/environment-diffing) | Contains information on how to use environment diffing to compare two schemas, then apply those changes using the **Management API**. | | [Schema as code](/docs/api-reference/schema/schema-as-code) | Contains information on how to export your schema in the form of code. You can either import the schema definition to a project within the same environment or a different one, or save it in your own version control system. | ## Content API | Document | Contents | | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Queries](/docs/api-reference/content-api/queries) | Contains information about working with queries. Hygraph automatically generates queries for fetching single and multiple entries for each defined content type belonging to your project. | | [Mutations](/docs/api-reference/content-api/mutations) | Contains information about working with mutations. Your project endpoint exposes GraphQL mutations you can use to modify the contents of your project. | | [Filtering](/docs/api-reference/content-api/filtering) | Contains information about using filters. Hygraph automatically creates filters for types you add to your content models. These filters can be applied to a single or multiple entries, and nested object fields. | | [Ordering](/docs/api-reference/content-api/ordering) | Contains information about ordering. When fetching multiple entries you can use the `orderBy` argument to define the order of the returned records. | | [Pagination](/docs/api-reference/content-api/pagination) | Contains information about pagination. Hygraph supports various arguments for paginating content entries. | | [Content stages](/docs/api-reference/content-api/content-stages) | Contains information about content stages. You can create your own content stages inside the Hygraph UI, and query content from these stages, as well as publish to. | | [Localization](/docs/api-reference/content-api/localization) | Contains information about localization. Hygraph boasts a flexible localization API that you can use to publish content for all or specific locales in your project. | | [Variables](/docs/api-reference/content-api/variables) | Contains information about working with variables, and how to use them to create dynamic queries and mutations. | | [Rich text](/docs/api-reference/content-api/rich-text-field) | Contains information on Hygraph's rich text field and how to use it to customize your front end. | | [Headers](/docs/api-reference/content-api/headers) | Contains information on use headers as a global way to send more specific requests to our API. | ## Assets | Document | Contents | | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Overview](/docs/api-reference/assets/assets-overview) | Overview to our asset upload API reference. | | [Fetching assets](/docs/api-reference/assets/fetching-assets) | Contains information about fetching assets using our Content API. | | [Referencing assets](/docs/api-reference/assets/referencing-assets) | Contains information about referencing assets using our asset upload API. | | [Transformations](/docs/api-reference/assets/transformations) | Contains information about passing transformations using our asset upload API: resize images, convert file type, validating transforms, combining transforms, alias transforms. | | [Uploading assets](/docs/api-reference/assets/uploading-assets) | Contains information about uploading assets using our asset upload API. | | [Updating assets](/docs/api-reference/assets/updating-assets) | Contains information about updating assets using our asset upload API. | | [Deleting assets](/docs/api-reference/assets/deleting-assets) | Contains information about deleting assets using our asset upload API. | | [Publishing assets](/docs/api-reference/assets/publishing-assets) | Contains information about publishing assets using our asset upload API. | | [Localized assets](/docs/api-reference/assets/localized-assets) | Contains information about uploading assets for your project locales using our asset upload API. | | [Embedded types](/docs/api-reference/assets/embedded-types) | Contains information about embedding assets into the Rich Text field type using our asset upload API. | ## Management SDK | Document | Contents | | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | | [Overview](/docs/api-reference/management-sdk/management-sdk) | Contains information about Hygraph's Management SDK. Learn about its advantages and how it works. | | [Quickstart](/docs/api-reference/management-sdk/management-sdk-quickstart) | Contains our Management SDK Quickstart, with information on installation, and usage. | | [Migration](/docs/api-reference/management-sdk/management-sdk-quickstart#migrate-from-the-previous-sdk) | Contains information on how to migrate from the previous SDK to the Management SDK. | | [Batch migration](/docs/api-reference/management-sdk/management-sdk-batchmigration) | Contains information on how to use the **Management SDK Method** for batch migrations. | | [Example](/docs/api-reference/management-sdk/management-sdk-example) | An example of building a complete blog platform schema using the Management SDK. | | [Method reference](/docs/api-reference/management-sdk/management-sdk-methods-reference) | A quick reference for every operation that you can carry out using the Management SDK. | ## Developer tools | Document / Repos | Contents | | ---------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | | Rich text | External link to our rich text renderer repo. | | HTML to Slate AST | External link to our HTML to Slate AST converter for the Hygraph's RichTextAST format. | | Gatsby Source Plugin | External link to our Gatsby Source Plugin. |     --- # Management SDK Source: https://hygraph.com/docs/api-reference/management-sdk/management-sdk The Hygraph Management SDK lets you programmatically manage your Hygraph project configuration, including schema, models, fields, taxonomies, workflows, webhooks, and editor UI elements. It provides a typed SDK that allows multiple schema-related actions to be **executed in order and in a single transaction**. It is designed for automation and repeatability, enabling you to manage Hygraph configuration as code rather than through manual UI changes. Unlike the Content API, which handles data entry, the Management SDK defines the architecture of your project. ## Key benefits The Management SDK is intended for use cases where manual configuration does not scale well. It enables you to: - Apply multiple schema-related changes. It allows accepting a set of changes which are applied on an “all or none” basis. So if anything fails in one of the events along the process, everything is rolled back automatically. - Bypass the web app, by allowing you to create a script that applies all the changes at once, without you having to procedurally create everything. - Apply the scripts you create to different environments. So they can be used in test environments to test schema changes upfront. In this scenario, you would make all the schema changes in this migration script, test it, then apply the script to the master environment when everything works as intended. - Store and review configuration changes in version control. Define schema and configuration changes as code and store them in a version control system alongside your application. This allows teams to review, audit, and apply changes consistently across environments using pull requests. - Run migrations safely as part of CI/CD pipelines. Use the Management SDK in deployment pipelines to apply schema changes automatically before shipping dependent code. Batch migrations ensure changes are validated and applied atomically, reducing the risk of partial or inconsistent updates. If you need to script or automate the Hygraph setup, the Management SDK is the recommended approach. ## How it works The SDK works in two modes. - **Write changes directly**: You initialize a client, call the methods that describe the schema you want, and then submit them to the Management server through the SDK. For more information, see the [quickstart guide](/docs/api-reference/management-sdk/management-sdk-quickstart). - **Sync changes between environments**: If you have a development environment where you've already made and tested schema changes, you can generate a diff and apply it directly to another environment. This mode replaces the target schema. It does not merge changes. Any schema changes that exist in the target environment but not in the source will be deleted. For more information, see the [batch migration guide](/docs/api-reference/management-sdk/management-sdk-batchmigration). In the above cases, the underlying method is the `submitBatchChanges` mutation. If any single change fails, the entire batch is rolled back automatically. ```ts submitBatchChanges(data: BatchMigrationInput!): AsyncOperationPayload! ``` This mutation takes an `environmentId`, an optional name, and a list of changes as arguments, then executes them in a single transaction. ## What you can manage The Management SDK covers project configuration, not content. You can use it to manage: - **Schema and models**: Models, fields, relations, remote fields - **Taxonomies**: Taxonomies and hierarchical taxonomy nodes - **Remote sources**: REST and GraphQL remote sources - **Workflows**: Content workflows and workflow stages - **Webhooks**: Webhook configuration and triggers - **Editor UI**: System and custom sidebar elements ## When to use the SDK Use the Management SDK when you need to: - Automate schema changes - Synchronize configuration between environments, such as `development`, `staging`, or `production` - Run schema migrations during deployments - Enforce consistency in large or distributed teams ## When not to use the SDK The Management SDK is not intended for: - Creating or updating content entries - Editorial workflows - Querying published content For content operations, use the [Content API](/docs/api-reference#content-api). ## Next steps - To get started, take a look at our [Quickstart](/docs/api-reference/management-sdk/management-sdk-quickstart). - Learn how to [migrate from the old SDK](/docs/api-reference/management-sdk/management-sdk-quickstart#migrate-from-the-previous-sdk). --- # Run batch migrations with the Management SDK Source: https://hygraph.com/docs/api-reference/management-sdk/management-sdk-batchmigration With batch migrations, you can apply multiple schema updates to an environment in a single operation. This workflow uses the `Client.getEnvironmentDiff` method to get the diff, then submits all required schema changes at once. The migration either succeeds completely or fails entirely. If any step fails, no changes are applied. This reduces the risk of partial or inconsistent updates. While supported and commonly used, submitting schema changes directly to production does not guarantee zero risk, especially when content already exists. ## How it works Submitting batch changes is a two-step process: 1. **Generate a diff**: The Management SDK compares the source environment with a target environment using `getEnvironmentDiff` and produces a list of schema operations required to make the target match the source. 2. **Submit the diff as a single batch**: The generated diff is submitted using `applySchemaChanges`, which applies all operations together in a single action. The batch either succeeds completely or fails entirely. If any operation fails validation or execution, the entire batch is rolled back. This workflow is commonly recommended for customers, who have two environments by default, such as those on the Pro plan. It works on any plan where you have at least two environments. ### Supported schema elements The following schema elements are supported for batch changes submissions:
- Models - Components - Locales - Simple fields - Conditional visibility in fields - Relational fields - Enumerations - Enumerable fields - Initial values in enumeration fields
- Stages - Union fields - Apps - Custom renderers and app fields - Sidebar elements - Remote fields - Remote type definitions - Remote sources
**Not supported:** UI extensions. ## Get the environment name First, you need the environment name. For this query, you need to provide the project ID. You can find the Project ID in [Project settings](/docs/developer-guides/project/manage-project-info) or in the project URL - `https://app.hygraph.com///`. 1. Navigate to the **API Playground** in your Hygraph project. 2. In the **API selector** dropdown, select the **Management API**. 3. Run the following query to get the environment names: ```graphql query MyQuery { viewer { project(id: "") { environments { name id } } } } ``` ## Create a Management SDK client Create a new file, for example `submit-batch-changes.ts`, and initialize the client with the following parameters: ```ts import { Client } from '@hygraph/management-sdk'; const client = new Client({ authToken, endpoint, name, // optional }); ``` | Option | Description | |-----------------------------|-----------------------------------------------------| | `authToken` | Permanent auth token for your project. This can be retrieved from your Hygraph project in **Project Settings > Permanent Auth Tokens > Token**. Make sure the token has proper Management API permissions depending on what you plan to execute via the SDK. | | `endpoint` | Endpoint of the High Performance Content API that belongs to the environment that you will work with. The URL can be retrieved from your Hygraph project in **Project Settings > Endpoints > High Performance Content API**. | | `name` | Optional identifier used for logging and debugging. Every migration has a unique name within an environment. If unspecified, a name will be generated and will be part of the response of a successful migration. Subsequent migrations with the same name in the same environment will fail. | For more information, [read this document](/docs/developer-guides/project/api-access). ## Generate a diff Use the `getEnvironmentDiff` method to generate a list of operations required to make the target environment match the specified source environment. In this example: - The client is initialized with the `master` environment's auth token and endpoint. This is the target environment where changes will be applied. - In the `getEnvironmentDiff` method, we pass `development` as the parameter. This is the source environment where changes come from. - The result is a list of operations that will make `master` (target) match `development` (source). ```ts const diff = await client.getEnvironmentDiff("development"); ``` This returns operations to sync schema changes from `development` (source) to `master` (target). ### Required fields in diffs When you make a field **required** in the source environment (`development`), existing content in the target environment (`master`) may contain `null` values for that field. To apply this change successfully, you must provide a migration value that replaces `null` for existing entries. ![Migration value in field validations](/images/docs/api-reference/schema/env-diffing-migrationvalue.png) When generating a diff, the Management SDK suggests updating the field to `required`, but it does not automatically include a migration value. If you apply the diff without adding one, the operation will fail. Before applying the diff, you must manually add a `migrationValue` to the corresponding change. For boolean fields, use `true` or `false` as the migration value instead of a string. ```json { "changes": [ { "createSimpleField": { "apiId": "newField", "parentApiId": "Post", "type": "STRING", "displayName": "NewField", "description": null, "initialValue": null, "tableRenderer": "GCMS_SINGLE_LINE", "formRenderer": "GCMS_SINGLE_LINE", "tableExtension": null, "formExtension": null, "formConfig": {}, "tableConfig": {}, "isList": false, "isLocalized": false, "isRequired": true, "isUnique": false, "isHidden": false, "embeddableModels": [], "visibility": "READ_WRITE", "isTitle": false, "position": 3, "validations": null, "embedsEnabled": null, "migrationValue": "value" } } ] } ``` The provided `migrationValue` replaces existing `null` values when batch changes are applied. ## Apply schema changes Use the following method to schedule the generated diff for the target environment: ```ts client.applySchemaChanges(diff); ``` This schedules all operations from the diff. ## Dry run a migration You can dry run your migration to preview what changes would be applied. ```ts const changes = client.dryRun(); console.log(changes); ``` Inspect the `changes` array and validate the list of operations that will be applied to the target environment. ## Run a migration in production The `run()` method executes the applied schema changes, making the target environment (`master`) match the source environment (`development`). ```ts const result = await client.run(true); if (result.errors) { console.log(result.errors); } else { console.log(result.name); } ``` ## Full example Here's a complete workflow showing source/target relationships. All schema changes from `development` (source) are applied to `master` (target), making `master` identical to `development`. ```ts import { Client } from '@hygraph/management-sdk'; async function syncEnvironments() { // Create client for TARGET environment (where changes will be applied) const targetClient = new Client({ authToken: '', endpoint: '', name: 'sync-dev-to-master' }); // Generate diff from SOURCE environment (where changes come from) const diff = await targetClient.getEnvironmentDiff("development"); // Apply schema changes to TARGET try { targetClient.applySchemaChanges(diff); const result = await targetClient.run(true); if (result.errors) { console.error('Migration failed with errors:', result.errors); return; } console.log('Successfully synced master to match development'); console.log(`Migration name: ${result.name}`); console.log(`Finished at: ${result.finishedAt}`); } catch (error) { console.error('Migration failed:', error); } } syncEnvironments(); ``` ## How schema changes affect the target environment Batch changes submitted with the Management SDK do not merge schemas. Instead, the target schema is replaced so that it exactly matches the source schema. The SDK first generates a diff using `getEnvironmentDiff`, which lists the operations required to transform the target environment (`master`) into the source environment (`development`). When this diff is applied, schema elements are added, modified, or deleted as needed to achieve an exact match. Any schema elements present in `master` but missing or different in `development`, including models, fields, and sidebar elements, may be overwritten or deleted. If a schema change exists only in `master`, the diff will include operations to remove or overwrite that change. To reduce the risk of unintended deletions or content loss: - Freeze schema changes in `master` while working in `development` - Keep schema changes mirrored across environments as you go - Always review the generated diff before applying it ### Example scenarios - You clone `master` to create `development`, make schema changes in `development`, and later make additional schema changes directly in `master`. When you generate and apply the diff, the SDK will suggest deleting the changes that exist only in `master`, because they are not present in `development`. - A field named **Title Field** exists in both environments, but you rename it to **Title** only in `master`. The diff will suggest deleting **Title** and recreating **Title Field**, which aligns the schemas but results in content loss. - You delete a field in `development` and then create a new field with the same name. The diff may not detect this as a deletion and recreation, even though the underlying field identity has changed. --- # Complete example Source: https://hygraph.com/docs/api-reference/management-sdk/management-sdk-example This guide walks you through building a complete blog platform schema using the Management SDK. You'll create models, components, relations, enumerations, and configure conditional visibility. ## What you'll build A blog platform with: - `Post` model with title, content, status, and a featured flag - `Author` model with name, bio, and email - `Category` model with name and description - `SeoMetadata` component (reusable SEO fields) - `AuthorInfo` component (social media info) - `CallToAction` and `ImageBlock` components (content blocks) - Post-to-Author relation (many-to-one) - Post-to-Category relation (many-to-many) - Component embeddings (SEO in Post, social info in `Author`) - Modular component field (flexible content blocks) - Nested components (ContactDetails nested inside `AuthorInfo`) - Status enumeration (`DRAFT`, `REVIEW`, `PUBLISHED`) - Conditional visibility rules ## Prerequisites Before you begin, ensure that you have the following: - Hygraph project created - [Permanent Auth Token](/docs/developer-guides/project/api-access#permanent-auth-tokens) with Management API permissions - Node.js 18 or later installed - Management SDK installed: `npm install @hygraph/management-sdk` - Content API endpoint from **Project Settings > Endpoints > High Performance Content API** ## Dependency order The Management SDK executes operations sequentially. You need to create dependencies before referencing them. For example: - Creating a relation before its target model exists will fail. - Creating an enumerable field before its enumeration exists will fail. - Embedding a component before it exists will fail. - Nesting a component before the parent component exists will fail. ``` Create models (Author, Category, Post), and add simple fields to them ↓ Create enumerations (PostStatus) ↓ Add enumerable field (status) to Post ↓ Create components (SeoMetadata, AuthorInfo, CallToAction, ImageBlock), and add simple fields to them ↓ Embed components in models (SeoMetadata in Post, AuthorInfo in Author) ↓ Create nested components (ContactDetails nested inside AuthorInfo) ↓ Create modular component field (contentBlocks in Post) ↓ Create relations (Post→Author, Post→Category) ↓ Conditional visibility rules ``` ## Initialize the client Create the `create-blog-schema.ts` file: ```ts import { Client } from '@hygraph/management-sdk'; const client = new Client({ authToken: '', endpoint: '', name: 'create-blog-schema-v1', // Unique migration name }); async function createBlogSchema() { try { // All operations will go here } catch (error) { console.error('Migration failed:', error); process.exit(1); } } createBlogSchema(); ``` ## Create models Models must exist before you can add fields to them. The values of `apiId` and `apiIdPlural` must be different. ```ts // Create Author model client.createModel({ apiId: 'Author', apiIdPlural: 'Authors', displayName: 'Author', description: 'Blog post authors', }); console.log('Created Author model'); ``` ```ts // Create Category model client.createModel({ apiId: 'Category', apiIdPlural: 'Categories', displayName: 'Category', description: 'Blog post categories', }); console.log('Created Category model'); ``` ```ts // Create Post model client.createModel({ apiId: 'Post', apiIdPlural: 'Posts', displayName: 'Post', description: 'Blog posts', }); console.log('Created Post model'); ``` ## Add fields to models Now, you can add fields to your models. Note the following points: - Only one field per model can be `isTitle: true`. This field appears as the entry identifier in the UI. - `initialValue` sets the default for new entries. - For boolean fields, use `true` or `false`. ```ts // Author: name (required, title field) client.createSimpleField({ parentApiId: 'Author', apiId: 'name', displayName: 'Name', type: SimpleFieldType.STRING, // Use enum, not string isRequired: true, isTitle: true, visibility: VisibilityTypes.READ_WRITE, // Optional but recommended description: 'Author full name', }); // Author: email (required, unique) client.createSimpleField({ parentApiId: 'Author', apiId: 'email', displayName: 'Email', type: SimpleFieldType.STRING, // Use enum isRequired: true, isUnique: true, visibility: VisibilityTypes.READ_WRITE, description: 'Author email address', validations: { String: { matches: { regex: '^([a-z0-9_\\.\\+-]+)@([\\da-z\\.-]+)\\.([a-z\\.]{2,6})$', errorMessage: 'Please enter a valid email address' } } } }); // Author: bio (optional, rich text) client.createSimpleField({ parentApiId: 'Author', apiId: 'bio', displayName: 'Bio', type: SimpleFieldType.RICHTEXT, // Use enum isRequired: false, visibility: VisibilityTypes.READ_WRITE, description: 'Author biography', }); console.log('Added fields to Author'); ``` ```ts // Category: name (required, title field) client.createSimpleField({ parentApiId: 'Category', apiId: 'name', displayName: 'Name', type: SimpleFieldType.STRING, // Use enum isRequired: true, isTitle: true, visibility: VisibilityTypes.READ_WRITE, description: 'Category name', }); // Category: description client.createSimpleField({ parentApiId: 'Category', apiId: 'description', displayName: 'Description', type: SimpleFieldType.STRING, // Use enum isRequired: false, visibility: VisibilityTypes.READ_WRITE, description: 'Category description', }); console.log('Added fields to Category'); ``` ```ts // Post: title (required, title field) client.createSimpleField({ parentApiId: 'Post', apiId: 'title', displayName: 'Title', type: SimpleFieldType.STRING, // Use enum isRequired: true, isTitle: true, visibility: VisibilityTypes.READ_WRITE, description: 'Post title', }); // Post: slug (required, unique) client.createSimpleField({ parentApiId: 'Post', apiId: 'slug', displayName: 'Slug', type: SimpleFieldType.STRING, // Use enum isRequired: true, isUnique: true, tableRenderer: 'GCMS_SLUG', formRenderer: 'GCMS_SLUG', visibility: VisibilityTypes.READ_WRITE, description: 'URL-friendly slug', }); // Post: content (rich text) client.createSimpleField({ parentApiId: 'Post', apiId: 'content', displayName: 'Content', type: SimpleFieldType.RICHTEXT, // Use enum isRequired: false, visibility: VisibilityTypes.READ_WRITE, description: 'Post content', }); // Post: excerpt (optional) client.createSimpleField({ parentApiId: 'Post', apiId: 'excerpt', displayName: 'Excerpt', type: SimpleFieldType.STRING, // Use enum isRequired: false, visibility: VisibilityTypes.READ_WRITE, description: 'Short summary for listings', }); // Post: featured (boolean, default false) client.createSimpleField({ parentApiId: 'Post', apiId: 'featured', displayName: 'Featured', type: SimpleFieldType.BOOLEAN, // Use enum isRequired: true, initialValue: 'false', // String, not boolean! visibility: VisibilityTypes.READ_WRITE, description: 'Show in featured section', }); console.log('Added fields to Post'); ``` ## Create an enumeration Create an enumeration before configuring fields that reference them. ```ts // Create PostStatus enumeration client.createEnumeration({ apiId: 'PostStatus', displayName: 'Post Status', description: 'Workflow status for blog posts', values: [ { apiId: 'DRAFT', displayName: 'Draft' }, { apiId: 'REVIEW', displayName: 'In Review' }, { apiId: 'PUBLISHED', displayName: 'Published' }, ], }); console.log('Created PostStatus enumeration'); ``` ## Add enumeration to model The enumeration, `PostStatus`, must already exist. The `initialValue` must match one of the enumeration's values. ```ts // Post: status (enumerable field) client.createEnumerableField({ parentApiId: 'Post', apiId: 'status', displayName: 'Status', enumerationApiId: 'PostStatus', isRequired: true, initialValue: 'DRAFT', description: 'Publication status', }); console.log('Added status enumeration to Post'); ``` ## Create components Components are reusable field groups that can be embedded in multiple models. Create components before embedding them in models. ```ts // Create SEO Metadata component client.createComponent({ apiId: 'SeoMetadata', apiIdPlural: 'SeoMetadatas', displayName: 'SEO Metadata', description: 'Search engine optimization fields', }); console.log('Created SeoMetadata component'); // Add fields to the component client.createSimpleField({ parentApiId: 'SeoMetadata', apiId: 'metaTitle', displayName: 'Meta Title', type: SimpleFieldType.STRING, // Use enum, not string isRequired: false, description: 'SEO title tag', validations: { String: { // Wrap in String object characters: { max: 60, errorMessage: 'Meta title should be under 60 characters', }, }, }, }); client.createSimpleField({ parentApiId: 'SeoMetadata', apiId: 'metaDescription', displayName: 'Meta Description', type: SimpleFieldType.STRING, // Use enum isRequired: false, formRenderer: 'GCMS_MULTILINE', description: 'SEO meta description', validations: { String: { // Wrap in String object characters: { max: 160, errorMessage: 'Meta description should be under 160 characters', }, }, }, }); client.createSimpleField({ parentApiId: 'SeoMetadata', apiId: 'keywords', displayName: 'Keywords', type: SimpleFieldType.STRING, // Use enum isList: true, isRequired: false, description: 'SEO keywords', }); client.createSimpleField({ parentApiId: 'SeoMetadata', apiId: 'noIndex', displayName: 'No Index', type: SimpleFieldType.BOOLEAN, // Use enum isRequired: true, initialValue: 'false', // String value, not boolean description: 'Prevent search engine indexing', }); console.log('Added fields to SeoMetadata component'); ``` ```ts // Create AuthorInfo component for flexible author details client.createComponent({ apiId: 'AuthorInfo', apiIdPlural: 'AuthorInfos', displayName: 'Author Info', description: 'Additional author information', }); client.createSimpleField({ parentApiId: 'AuthorInfo', apiId: 'website', displayName: 'Website', type: SimpleFieldType.STRING, // Use enum isRequired: false, description: 'Author website URL', }); client.createSimpleField({ parentApiId: 'AuthorInfo', apiId: 'twitter', displayName: 'Twitter Handle', type: SimpleFieldType.STRING, // Use enum isRequired: false, description: 'Twitter username', }); client.createSimpleField({ parentApiId: 'AuthorInfo', apiId: 'location', displayName: 'Location', type: SimpleFieldType.STRING, // Use enum isRequired: false, description: 'Author location', }); console.log('Created AuthorInfo component'); ``` ```ts // Create components for page builder client.createComponent({ apiId: 'CallToAction', apiIdPlural: 'CallToActions', displayName: 'Call to Action', description: 'CTA block with button', }); client.createSimpleField({ parentApiId: 'CallToAction', apiId: 'heading', displayName: 'Heading', type: SimpleFieldType.STRING, // Use enum isRequired: true, description: 'CTA heading', }); client.createSimpleField({ parentApiId: 'CallToAction', apiId: 'buttonText', displayName: 'Button Text', type: SimpleFieldType.STRING, // Use enum isRequired: true, description: 'Button label', }); client.createSimpleField({ parentApiId: 'CallToAction', apiId: 'buttonUrl', displayName: 'Button URL', type: SimpleFieldType.STRING, // Use enum isRequired: true, description: 'Button destination URL', }); console.log('Created CallToAction component'); ``` ```ts // Create components for page builder client.createComponent({ apiId: 'ImageBlock', apiIdPlural: 'ImageBlocks', displayName: 'Image Block', description: 'Image with caption', }); client.createSimpleField({ parentApiId: 'ImageBlock', apiId: 'imageUrl', displayName: 'Image URL', type: SimpleFieldType.STRING, // Use enum isRequired: true, description: 'Image source URL', }); client.createSimpleField({ parentApiId: 'ImageBlock', apiId: 'caption', displayName: 'Caption', type: SimpleFieldType.STRING, // Use enum isRequired: false, description: 'Image caption', }); client.createSimpleField({ parentApiId: 'ImageBlock', apiId: 'altText', displayName: 'Alt Text', type: SimpleFieldType.STRING, // Use enum isRequired: true, description: 'Accessibility text', }); console.log('Created ImageBlock component'); ``` ## Embed component in model Now let's embed the SEO component into the `Post` model. You can add SEO metadata to each post without duplicating fields across models. ```ts // Post: Embed SeoMetadata component client.createComponentField({ parentApiId: 'Post', apiId: 'seo', displayName: 'SEO', componentApiId: 'SeoMetadata', isRequired: false, isList: false, description: 'SEO metadata for search engines', }); console.log('Embedded SEO component in Post'); ``` ## Create modular component field Modular components allow editors to choose from multiple component types, perfect for page builders. Editors can now add multiple `CallToAction` and `ImageBlock` components in any order within a post. A post might have: - `ImageBlock`: A hero image or an infographic - `CallToAction`: Subscribe prompt or download guide ```ts // Post: Content blocks (modular component) client.createComponentUnionField({ parentApiId: 'Post', apiId: 'contentBlocks', displayName: 'Content Blocks', componentApiIds: ['CallToAction', 'ImageBlock'], isList: true, isRequired: false, description: 'Flexible content blocks for rich posts', }); console.log('Created modular component field'); ``` ## Create nested components Components can be nested inside other components for deeply hierarchical structures. Let's create a nested `Address` component. In this example, notice `parentApiId: 'AuthorInfo'`. We're nesting a component inside another component, not inside a model. This creates a two-level hierarchy: `Author → AuthorInfo → ContactDetails`. ```ts // Create child component client.createComponent({ apiId: 'ContactDetails', apiIdPlural: 'ContactDetailsCollection', displayName: 'Contact Details', description: 'Phone and email contact information', }); // Add fields to the component client.createSimpleField({ parentApiId: 'ContactDetails', apiId: 'phone', displayName: 'Phone', type: SimpleFieldType.STRING, isRequired: false, description: 'Contact phone number', visibility: VisibilityTypes.READ_WRITE }); client.createSimpleField({ parentApiId: 'ContactDetails', apiId: 'email', displayName: 'Email', type: SimpleFieldType.STRING, isRequired: true, // Email is required description: 'Contact email address', validations: { String: { matches: { regex: '^([a-z0-9_\\.\\+-]+)@([\\da-z\\.-]+)\\.([a-z\\.]{2,6})$', errorMessage: 'Please enter a valid email address' } } } }); console.log('Created ContactDetails component'); // Nest ContactDetails inside AuthorInfo component client.createComponentField({ parentApiId: 'AuthorInfo', apiId: 'contact', displayName: 'Contact', componentApiId: 'ContactDetails', isRequired: false, isList: false, // Single contact (or true for multiple) description: 'Contact information', visibility: VisibilityTypes.READ_WRITE }); console.log('Nested ContactDetails inside AuthorInfo'); ``` **Result structure:** ``` Author (model) └── AuthorInfo (component) ├── website (STRING field) ├── twitter (STRING field) ├── location (STRING field) └── contact (component field) ← NESTED └── ContactDetails (component) ├── phone (STRING field) └── email (STRING field) ``` ## Create a many-to-one reference One author can write multiple posts. For this use case, we need to create a **many-to-one** relation. ```ts client.createRelationalField({ parentApiId: 'Post', apiId: 'author', displayName: 'Author', type: RelationalFieldType.RELATION, // Use enum, not string reverseField: { apiId: 'posts', modelApiId: 'Author', // Required! Specify the related model displayName: 'Posts', isList: true, // Required! Author has many posts visibility: VisibilityTypes.READ_WRITE // Use visibility instead of isHidden }, isList: false, // Post has one author isRequired: false, // Cannot be required for RELATION type (only ASSET) description: 'Post author' }); console.log('Created Post→Author relation'); ``` ## Create a many-to-many reference A post can belong to multiple categories. A category can have multiple posts. For this use case, we need to create a **many-to-many** relation. ```ts client.createRelationalField({ parentApiId: 'Post', apiId: 'categories', displayName: 'Categories', type: RelationalFieldType.RELATION, // Use enum, not string reverseField: { apiId: 'posts', modelApiId: 'Category', // Required! Specify the related model here displayName: 'Posts', isList: true, // Required! Category has many posts visibility: VisibilityTypes.READ_WRITE // Use visibility instead of isHidden }, isList: true, isRequired: false, description: 'Post categories' }); console.log('Created Post→Category relation'); ``` ## Add conditional visibility Make the `excerpt` field required only when `status` is `PUBLISHED`. Otherwise, it remains optional. ```ts client.updateSimpleField({ apiId: "excerpt", parentApiId: "Post", isRequired: true, visibility: VisibilityTypes.READ_WRITE, visibilityCondition: { baseField: "status", // API ID of the enumerable field (not the enum name) operator: FieldConditionOperator.IS, enumerationValues: ["PUBLISHED"], booleanValue: null } }); console.log('Configured conditional visibility on excerpt'); ``` ## Advanced features ### Add a taxonomy Taxonomies organize content into hierarchical categories. Let's add a category taxonomy and add the taxonomy to the `Post` model. Posts can now be organized using a hierarchical category tree. ```ts // Create the taxonomy with all nodes at once client.createTaxonomy({ apiId: 'BlogCategories', displayName: 'Blog Categories', description: 'Hierarchical blog categorization', taxonomyNodes: [ // Top-level categories (parent: null) { apiId: 'technology', displayName: 'Technology', parent: null }, { apiId: 'business', displayName: 'Business', parent: null }, // Subcategories { apiId: 'webDev', displayName: 'Web Development', parent: 'technology' }, { apiId: 'aiMl', displayName: 'AI & Machine Learning', parent: 'technology' } ] }); // Add taxonomy field to Post model client.createTaxonomyField({ parentApiId: 'Post', apiId: 'taxonomyCategories', displayName: 'Taxonomy Categories', taxonomyApiId: 'BlogCategories', isList: true, isRequired: false, description: 'Hierarchical categorization' }); console.log('Created taxonomy and taxonomy field'); ``` ### Add a workflow Create an editorial workflow for content approval. Posts can move through the draft → review → approved stages before publication. ```ts client.createWorkflow({ apiId: 'editorialWorkflow', displayName: 'Editorial Workflow', description: 'Content review and approval process', enabled: true, // Required! steps: [ // Not "stages"! { apiId: 'draft', displayName: 'Draft', description: 'Work in progress', color: ColorPalette.BLUE, // Use enum, not string allowEdit: true, // Required! allowedRoles: ['role-id-1'], // Required! Array of role IDs position: 0 }, { apiId: 'review', displayName: 'In Review', description: 'Awaiting editorial review', color: ColorPalette.YELLOW, // Use enum allowEdit: false, // Required! returnToStep: 'draft', // Can return to draft if rejected allowedRoles: ['role-id-2'], // Required! position: 1 }, { apiId: 'approved', displayName: 'Approved', description: 'Ready for publication', color: ColorPalette.GREEN, // Use enum allowEdit: false, // Required! allowedRoles: ['role-id-3'], // Required! publishStages: ['published'], // Optional: stages that can be published from this step position: 2 }, ], }); console.log('Created editorial workflow'); ``` ### Add a webhook Notify external systems when posts are published. Marketing system receives notifications when posts are published. ```ts client.createWebhook({ name: 'Post Publish Notification', description: 'Notify marketing system when blog posts are published', url: 'https://api.marketing.example.com/webhooks/blog-published', method: WebhookMethod.POST, headers: { Authorization: 'Bearer webhook-secret-token', 'Content-Type': 'application/json', }, isActive: true, includePayload: true, models: [], // Empty array = all models (including future ones) stages: [], // Empty array = all stages (including future ones) triggerType: WebhookTriggerType.CONTENT_MODEL, triggerActions: [WebhookTriggerAction.PUBLISH], secretKey: 'webhook-secret-key' }); ``` ## Test with dry run Before applying changes to production, test the migration: ```ts // At the start of createBlogSchema(), before try block: const changes = client.dryRun(); console.log('=== DRY RUN RESULTS ==='); console.log(`Operations: ${changes.length}`); console.log(JSON.stringify(changes, null, 2)); console.log('======================'); // Exit without running process.exit(0); ``` Review the output to ensure all operations are correct. Then remove the dry run code and proceed to production. ## Run the migration ```ts async function createBlogSchema() { try { // ... all operations here ... // Execute migration const result = await client.run(true); if (result.errors) { console.error('Migration failed with errors:', result.errors); process.exit(1); } console.log('Migration completed successfully'); console.log(`Migration name: ${result.name}`); console.log(`Finished at: ${result.finishedAt}`); } catch (error) { console.error('Migration failed:', error); process.exit(1); } } ``` Run the script: ```bash export HYGRAPH_AUTH_TOKEN="your-token" export HYGRAPH_ENDPOINT="https://your-region.hygraph.com/v2/..." node create-blog-schema.ts ``` ## Integration test Test the complete workflow: 1. **Create Author** with social info and nested contact details 2. **Create Categories** 3. **Create Post** with all features: - Set status to DRAFT (excerpt optional) - Fill in title, slug, content, featured flag - Select author and categories (relations) - Select taxonomy categories (hierarchical) - Add SEO metadata (metaTitle, metaDescription, keywords) - Add content blocks (CallToAction and ImageBlock components) 4. **Test conditional visibility:** Change status to PUBLISHED, verify excerpt becomes required 5. **Test workflow:** Move through stages if configured 6. **Test webhook:** Publish post and verify notification sent 7. **Verify final result:** Post displays with author (nested contact), categories, taxonomy, SEO, content blocks, and all features working together ## Troubleshooting ### Wrong operation order Operations must be executed in the correct order. For example, creating a relation before the target model exists will fail. ```ts client.createRelationalField({ parentApiId: 'Post', relatedModelApiId: 'Author', // Author doesn't exist yet // ... }); client.createModel({ apiId: 'Author' /* ... */ }); ``` ```ts client.createModel({ apiId: 'Author' /* ... */ }); client.createRelationalField({ parentApiId: 'Post', relatedModelApiId: 'Author', // Author exists now // ... }); ``` ### Reused migration names Use unique names for each migration, such as `create-blog-schema-v1`, `create-blog-schema-v2`, etc. Reusing names will cause the migration to fail. ```ts const client = new Client({ name: 'create-blog-schema', // First run: OK // Second run with same name: FAILS }); ``` ### apiId and apiIdPlural are the same The values of `apiId` and `apiIdPlural` of a model must be different. ```ts client.createModel({ apiId: 'Post', apiIdPlural: 'Post', // Must differ }); ``` ```ts client.createModel({ apiId: 'Post', apiIdPlural: 'Posts', }); ``` ### Multiple title fields in a model You can only have one `isTitle: true` field per model. This field is used as the entry identifier in the UI. ```ts client.createSimpleField({ parentApiId: 'Post', apiId: 'title', isTitle: true, // First title field - OK }); client.createSimpleField({ parentApiId: 'Post', apiId: 'name', isTitle: true, // Second title field - FAILS }); ``` ```ts client.createSimpleField({ parentApiId: 'Post', apiId: 'title', isTitle: true, // First title field - OK }); client.createSimpleField({ parentApiId: 'Post', apiId: 'name', }); ``` ## Next steps Review the [Methods Reference](/docs/api-reference/management-sdk/management-sdk-methods-reference) for all available Management SDK operations. --- # Management SDK methods reference Source: https://hygraph.com/docs/api-reference/management-sdk/management-sdk-methods-reference This document is a quick reference for every operation that you can carry out using the Management SDK. For a tutorial-style guide, see [this document](/docs/api-reference/management-sdk/management-sdk-example). All `Create`, `Update`, and `Delete` actions require **Update and Delete Existing Fields** permissions set in the Permanent Auth Token. ## Important notes **Data loss warnings:** - Converting fields to/from lists (`isList`) may cause data loss for existing entries. - Changing `isRequired` to true requires `migrationValue` for existing entries with null values. - Using `isUnique` with `initialValue` or `migrationValue` is forbidden (unique values must differ per entry). - Converting field types may result in data loss or validation failures. **Cardinality (Relational fields):** - Relation cardinality is set at creation and cannot be changed later. - `isList: false` + reverse `isList: false` = one-to-one. - `isList: false` + reverse `isList: true` = one-to-many. - `isList: true` + reverse `isList: true` = many-to-many. - For components: reverse side must have `isList: true` to allow reconnection. **Naming conventions:** - Models/components: `PascalCase` (Examples: `BlogPost`, `SeoMetadata`) - Fields: `camelCase` (Examples: `title`, `publishedAt`) - Enumerations: `PascalCase` (Examples: `PostStatus`) - Enum values: `UPPER_SNAKE_CASE` (Examples: `DRAFT`, `PUBLISHED`) ## Supported operations All operations that can be executed by the SDK are listed in the TypeScript Type Definitions, Client.d.ts file. ## Models ### createModel() Creates a new content model. **Use cases:** - Setting up new content types for your application, such as `BlogPost`, `Product`, `Author`. - Creating models for different content sections, such as `Pages`, `Articles`, `Events`. - Initializing models with custom sidebar elements from apps, such as `Analytics Dashboard`, `Content Workflows`, `Variants`. **Important notes:** - `apiId` and `apiIdPlural` must be different (cannot be the same value). - `apiId` must be PascalCase and start with uppercase letter. - Models must have unique `apiId` and `displayName` within an environment. - Cannot create required fields on system models/components. - Sidebar elements can be added during creation or via `updateModel`. ```ts client.createModel({ apiId: string, // Required: The model API ID apiIdPlural: string, // Required: The plural API ID (used for lists) displayName: string, // Required: Display name shown in the webapp description?: string, // Optional: Description of the model isSystem?: boolean, // Optional: Only AppTokens should provide this flag sidebarElements?: Array<{ // Optional: Sidebar elements to create displayName: string, // Required: Display name for the sidebar element description?: string, // Optional: Description for the sidebar element config?: JSON, // Optional: JSON metadata associated with the sidebar element appElementApiId: string, // Required: API ID of the App element appApiId: string, // Required: API ID of the App position?: number // Optional: Position of the sidebar element }> }) ``` ```ts // Basic model creation client.createModel({ apiId: 'Post', apiIdPlural: 'Posts', displayName: 'Blog Post', description: 'Blog posts for the website', }); // Model with custom sidebar elements client.createModel({ apiId: 'Product', apiIdPlural: 'Products', displayName: 'Product', description: 'E-commerce products', sidebarElements: [ { displayName: 'Product Analytics', description: 'View product analytics', appElementApiId: 'analytics-dashboard', appApiId: 'analytics-app', position: 0, config: { refreshInterval: 30 }, }, ], }); ``` ### updateModel() Updates an existing content model. **Use cases:** - Renaming models (using `newApiId`). - Updating model descriptions or display names - Managing sidebar elements (adding, updating, removing custom and system sidebar elements). - Adding app integrations to models. **Important notes:** - Renaming with newApiId updates all references to the model. - Sidebar elements can be managed via sidebarElementsToUpsert structure. - System sidebar elements include: `INFORMATION`, `STAGES`, `LOCALIZATIONS`, `VERSIONS`, `PREVIEW_URLS`, `RELEASES`, `CONTENT_WORKFLOWS`, `VARIANTS`. - Custom sidebar elements require `appApiId` and `appElementApiId`. ```ts client.updateModel({ apiId: string, // Required: Current API ID of the model newApiId?: string, // Optional: New API ID (to rename the model) apiIdPlural?: string, // Optional: New plural API ID displayName?: string, // Optional: New display name description?: string, // Optional: New description isSystem?: boolean, // Optional: System model flag (only for AppTokens) sidebarElementsToUpsert?: { // Optional: Sidebar elements to create/update/delete customSidebarElementsToCreate?: Array<{ displayName: string, // Required: Display name description?: string, // Optional: Description config?: JSON, // Optional: JSON metadata appElementApiId: string, // Required: API ID of the App element appApiId: string, // Required: API ID of the App position?: number // Optional: Position }>, systemSidebarElementsToCreate?: Array<{ type: SystemSidebarElementType, // Required: System sidebar element type config?: JSON, // Optional: JSON metadata position?: number // Optional: Position }>, sidebarElementsToUpdate?: Array<{ displayName: string, // Required: Current display name (identifier) newDisplayName?: string, // Optional: New display name description?: string, // Optional: New description config?: JSON, // Optional: New config position?: number // Optional: New position }>, customSidebarElementsToDelete?: Array<{ appApiId: string, // Required: API ID of the App appElementApiId: string // Required: API ID of the App element }>, systemSidebarElementsToDelete?: Array<{ type: SystemSidebarElementType // Required: System sidebar element type }> } }) ``` ```ts // Basic update - rename model and change display name client.updateModel({ apiId: 'Post', newApiId: 'BlogPost', displayName: 'Blog Post', description: 'Updated description', }); // Update with sidebar elements management client.updateModel({ apiId: 'Product', displayName: 'Product', sidebarElementsToUpsert: { customSidebarElementsToCreate: [ { displayName: 'Product Analytics', description: 'View analytics', appElementApiId: 'analytics-dashboard', appApiId: 'analytics-app', position: 0, }, ], systemSidebarElementsToCreate: [ { type: SystemSidebarElementType.INFORMATION, position: 1, }, ], sidebarElementsToUpdate: [ { displayName: 'Product Analytics', newDisplayName: 'Analytics Dashboard', description: 'Updated analytics dashboard', position: 2, }, ], customSidebarElementsToDelete: [ { appApiId: 'old-app', appElementApiId: 'old-element', }, ], systemSidebarElementsToDelete: [ { type: SystemSidebarElementType.VERSIONS, }, ], }, }); ``` - `SystemSidebarElementType` - `INFORMATION`, `STAGES`, `LOCALIZATIONS`, `VERSIONS`, `PREVIEW_URLS`, `RELEASES`, `CONTENT_WORKFLOWS`, `VARIANTS` ### deleteModel() Deletes a content model. **Use cases:** - Removing unused content models. - Cleaning up test/development models. - Restructuring content architecture. **Important notes:** - Permanent deletion. This action cannot be undone. - Deletes all entries in the model. - Deletes all fields associated with the model. - Delete the model only if there are no other models that depend on it. ```ts client.deleteModel({ apiId: string // Required: The API ID of the model to delete }) ``` ```ts // Delete a model client.deleteModel({ apiId: 'Post', }); console.log('Model deleted'); ``` ## Simple fields ### createSimpleField() Creates a simple field on a model or component. **Use cases:** - Adding text fields (title, description, content), such as `title`, `description`, `content` in the `Post` model . - Creating date/time fields (publishedAt, eventDate), such as `publishedAt`, `eventDate` in the `Post` model. - Setting up boolean flags (featured, published, active), such as `featured`, `published`, `active` in the `Post` model. - Creating numeric fields (price, rating, count), such as `price`, `rating`, `count` in the `Post` model. - Adding rich text content fields, such as `content` in the `Post` model. - Creating slug fields with auto-generation, such as `slug` in the `Post` model. - Setting up email/URL fields with validation, such as `email`, `url` in the `Post` model. - Creating hidden fields for internal tracking, such as `internalTracking` in the `Post` model. **Important notes:** - Field `apiId` must be camelCase and start with lowercase letter. - Reserved field names: `createdAt`, `createdBy`, `documentInStages`, `history`, `id`, `locale`, `localizations`, `publish`, `publishedAt`, `publishedBy`, `status`, `updatedAt`, `updatedBy`, `versions`. - `isUnique: true` cannot be combined with `initialValue` or `migrationValue`. - RichText fields cannot be marked as title (`isTitle: true`). - List RichText fields cannot be required (`isList: true + isRequired: true`). - ID type fields are validated as CUIDs and stored as `varchar(25)`. - `initialValue` must be stringified for non-string types (e.g., `'false'` for boolean). ```ts client.createSimpleField({ apiId: string, // Required: Field API ID parentApiId: string, // Required: API ID of parent model/component (use instead of modelApiId) modelApiId?: string, // Deprecated: Use parentApiId instead type: SimpleFieldType, // Required: Field type enum displayName: string, // Required: Display name description?: string, // Optional: Description isVariantEnabled?: boolean, // Optional: Enable variant support initialValue?: string, // Optional: Default value (stringified for non-strings) tableRenderer?: string, // Optional: Table renderer identifier formRenderer?: string, // Optional: Form renderer identifier. Supported values: `GCMS_SINGLE_LINE`, `GCMS_MULTI_LINE`, `GCMS_SLUG`, or `GCMS_MARKDOWN` tableExtension?: string, // Optional: Table extension identifier formExtension?: string, // Optional: Form extension identifier formConfig?: JSON, // Optional: Form configuration JSON tableConfig?: JSON, // Optional: Table configuration JSON isList?: boolean, // Optional: Whether field accepts multiple values (default: false) // WARNING: Converting existing fields to/from list may cause data loss isLocalized?: boolean, // Optional: Whether field is localized (default: false) isRequired?: boolean, // Optional: Whether field is required (default: false) // When changing existing field to true, must provide migrationValue isUnique?: boolean, // Optional: Whether field is unique (default: false) // CANNOT be combined with initialValue or migrationValue // Unique values must differ per entry isHidden?: boolean, // Deprecated: Use visibility instead visibility?: VisibilityTypes, // Optional: Visibility setting isTitle?: boolean, // Optional: Whether field is used as title (default: false) position?: number, // Optional: Field position validations?: { // Optional: Field validations with custom error messages Int?: { range?: { // Validate numeric range min?: number, // Minimum value (inclusive) max?: number, // Maximum value (inclusive) errorMessage?: string // Custom error shown to users }, listItemCount?: { // Validate list length (when isList is true) min?: number, // Minimum items required max?: number, // Maximum items allowed errorMessage?: string } }, String?: { characters?: { // Validate string length min?: number, // Minimum characters max?: number, // Maximum characters errorMessage?: string }, matches?: { // Validate with regex pattern (must match) regex: string, // Regex pattern (e.g., email format) flags?: string[], // Regex flags (e.g., ['i'] for case-insensitive) errorMessage?: string }, notMatches?: { // Validate with regex pattern (must NOT match) regex: string, // Regex pattern to reject flags?: string[], errorMessage?: string } } }, migrationValue?: string, // Optional: Value to set for existing null entries // Required when changing isRequired to true on existing fields // Cannot be combined with isUnique (unique values must differ per entry) embedsEnabled?: boolean, // Optional: Enable rich text embeds embeddableModels?: string[], // Required when embedsEnabled is true // Array of model API IDs that can be embedded visibilityCondition?: { // Optional: Conditional visibility - show/hide field based on another field's value baseField: string, // Required: API ID of the field that controls visibility operator: FieldConditionOperator, // Required: Comparison operator (IS, IS_NOT, etc.) enumerationValues?: string[], // Required when baseField is enumeration type booleanValue?: boolean // Required when baseField is boolean type }, isSystem?: boolean // Optional: System field flag (only for AppTokens) }) ``` ```ts // Basic string field client.createSimpleField({ parentApiId: 'Post', apiId: 'title', displayName: 'Title', type: SimpleFieldType.STRING, isRequired: true, isTitle: true, }); // String field with validations client.createSimpleField({ parentApiId: 'Post', apiId: 'slug', displayName: 'Slug', type: SimpleFieldType.STRING, isRequired: true, isUnique: true, validations: { String: { characters: { max: 100, errorMessage: 'Slug must be under 100 characters' }, matches: { regex: '^[a-z0-9-]+$', flags: [], errorMessage: 'Invalid slug format' }, }, }, }); // Slug field with validations client.createSimpleField({ parentApiId: 'Page', type: SimpleFieldType.STRING, apiId: 'slug', displayName: 'Slug', description: 'Enter the slug for this page, such as about, blog, or contact', isRequired: true, isUnique: true, tableRenderer: 'GCMS_SLUG', formRenderer: 'GCMS_SLUG', validations: { String: { characters: { max: 100, errorMessage: 'Slug must be under 100 characters' }, matches: { regex: '^[a-z0-9-]+$', flags: [], errorMessage: 'Slug can only contain lowercase letters, numbers, and hyphens' }, }, }, }); // Richtext field client.createSimpleField({ parentApiId: '', type: SimpleFieldType.RICHTEXT, apiId: '', displayName: '', description: '', isRequired: true, }); // Rich text field with embeds client.createSimpleField({ parentApiId: 'Post', apiId: 'content', displayName: 'Content', type: SimpleFieldType.RICHTEXT, embedsEnabled: true, embeddableModels: ['Author', 'Image'], }); // Boolean field with initial value client.createSimpleField({ parentApiId: 'Post', apiId: 'featured', displayName: 'Featured', type: SimpleFieldType.BOOLEAN, isRequired: true, initialValue: 'false', // Must be stringified }); // Hidden integer field with custom field validation client.createSimpleField({ parentApiId: 'Product', type: SimpleFieldType.INT, apiId: 'viewCount', displayName: 'View Count', visibility: VisibilityTypes.HIDDEN, validations: { Int: { range: { max: 1000, min: 0, errorMessage: 'Counter has to be between 0 and 1000', }, }, }, }); // Required and unique email field with validation client.createSimpleField({ parentApiId: 'User', type: SimpleFieldType.STRING, apiId: 'email', displayName: 'Email', description: 'User email address', isRequired: true, isUnique: true, validations: { String: { matches: { regex: '^([a-z0-9_\\.\\+-]+)@([\\da-z\\.-]+)\\.([a-z\\.]{2,6})$', flags: ['i'], // Case-insensitive matching errorMessage: 'Please enter a valid email address', }, }, }, }); // Create a list of date times for event dates client.createSimpleField({ parentApiId: 'Post', type: SimpleFieldType.DATETIME, apiId: 'scheduledPublishDates', displayName: 'Scheduled Publish Dates', description: 'Multiple dates when this post should be published', isRequired: false, isList: true, }); // Add a field to a component client.createSimpleField({ parentApiId: 'SeoMetadata', type: SimpleFieldType.STRING, apiId: 'metaTitle', displayName: 'Meta Title', }); ``` **Enums** - `SimpleFieldType`: `ID`, `STRING`, `RICHTEXT`, `INT`, `FLOAT`, `BOOLEAN`, `JSON`, `DATETIME`, `DATE`, `LOCATION`, `COLOR` - `VisibilityTypes`: `READ_WRITE`, `READ_ONLY`, `HIDDEN`, `API_ONLY` - `FieldConditionOperator`: `IS`, `IS_NOT`, `CONTAINS_ANY`, `CONTAINS_ALL`, `CONTAINS_NONE` ### updateSimpleField() Updates an existing simple field. **Use cases:** - Renaming fields - Adding/updating validations - Changing field visibility - Updating embeddable models for rich text fields - Adding conditional visibility - Changing required/unique status **Important notes:** - Field type cannot be changed (immutable). - `embeddableModels` uses add/remove structure: `{ modelsToAdd: [], modelsToRemove: [] }`. - When changing `isRequired` to true, must provide `migrationValue` for existing null entries. - Cannot change `isList` without potential data loss. - `visibilityCondition` can be set to `null` to remove it. ```ts client.updateSimpleField({ apiId: string, // Required: Current field API ID newApiId?: string, // Optional: New API ID (to rename the field) parentApiId?: string, // Optional: Parent model/component API ID (use instead of modelApiId) modelApiId?: string, // Deprecated: Use parentApiId instead displayName?: string, // Optional: New display name description?: string, // Optional: New description isVariantEnabled?: boolean, // Optional: Enable/disable variant support isList?: boolean, // Optional: Whether field accepts multiple values (default: false) // WARNING: Converting existing fields to/from list may cause data loss isLocalized?: boolean, // Optional: Whether field is localized isRequired?: boolean, // Optional: Whether field is required // When changing to true, provide migrationValue for existing null entries isUnique?: boolean, // Optional: Whether field is unique // CANNOT be used with initialValue or migrationValue isHidden?: boolean, // Deprecated: Use visibility instead visibility?: VisibilityTypes, // Optional: Visibility setting isTitle?: boolean, // Optional: Whether field is used as title position?: number, // Optional: Field position initialValue?: string, // Optional: Default value (stringified) migrationValue?: string, // Optional: Migration value for existing data // Required when changing isRequired to true on existing fields validations?: { // Optional: Field validations (same structure as create) Int?: { range?: { min?: number, max?: number, errorMessage?: string }, listItemCount?: { min?: number, max?: number, errorMessage?: string } }, Float?: { range?: { min?: number, max?: number, errorMessage?: string }, listItemCount?: { min?: number, max?: number, errorMessage?: string } }, String?: { characters?: { min?: number, max?: number, errorMessage?: string }, listItemCount?: { min?: number, max?: number, errorMessage?: string }, matches?: { regex: string, flags?: string[], errorMessage?: string }, notMatches?: { regex: string, flags?: string[], errorMessage?: string } } }, embedsEnabled?: boolean, // Optional: Enable rich text embeds embeddableModels?: { // Optional: Manage embeddable models (different from create) modelsToAdd?: string[], // Model API IDs to add to existing list modelsToRemove?: string[] // Model API IDs to remove from existing list }, // NOTE: Different structure from create! tableRenderer?: string, // Optional: Table renderer identifier formRenderer?: string, // Optional: Form renderer identifier. Supported values: `GCMS_SINGLE_LINE`, `GCMS_MULTI_LINE`, `GCMS_SLUG`, or `GCMS_MARKDOWN` tableExtension?: string, // Optional: Table extension identifier formExtension?: string, // Optional: Form extension identifier formConfig?: JSON, // Optional: Form configuration JSON tableConfig?: JSON, // Optional: Table configuration JSON visibilityCondition?: { // Optional: Show/hide field based on another field's value baseField: string, // Required: API ID of the field that controls visibility operator: FieldConditionOperator, // Required: Comparison operator (IS, IS_NOT, etc.) enumerationValues?: string[], // Required when baseField is enumeration type booleanValue?: boolean // Required when baseField is boolean type }, isSystem?: boolean // Optional: System field flag (only for AppTokens) }) ``` ```ts // Basic update - rename field and change display name client.updateSimpleField({ apiId: 'title', newApiId: 'postTitle', displayName: 'Post Title', }); // Update validations client.updateSimpleField({ apiId: 'slug', validations: { String: { characters: { max: 150, errorMessage: 'Slug must be under 150 characters' }, }, }, }); // Update embeddable models (add/remove models) client.updateSimpleField({ apiId: 'content', embedsEnabled: true, embeddableModels: { modelsToAdd: ['Video', 'Quote'], modelsToRemove: ['Image'], }, }); ``` **Enums** - `SimpleFieldType`: `ID`, `STRING`, `RICHTEXT`, `INT`, `FLOAT`, `BOOLEAN`, `JSON`, `DATETIME`, `DATE`, `LOCATION`, `COLOR` - `VisibilityTypes`: `READ_WRITE`, `READ_ONLY`, `HIDDEN`, `API_ONLY` - `FieldConditionOperator`: `IS`, `IS_NOT`, `CONTAINS_ANY`, `CONTAINS_ALL`, `CONTAINS_NONE` ### deleteField() Deletes one of the following field types from a model or component:
- Simple fields - Enumerable fields - Component fields - Component union fields
- Relational fields - Union fields - Remote fields - Taxonomy fields
**Use cases:** - Removing unused fields. - Cleaning up deprecated fields. - Restructuring content models. **Important notes:** - Permanent deletion. This action cannot be undone. - Deletes all field data for all entries. - May break conditional visibility dependencies. - Cannot delete required fields (make optional first). ```ts client.deleteField({ apiId: string, // Required: The API ID of the field to delete parentApiId?: string, // Optional: API ID of parent model/component (use instead of modelApiId) modelApiId?: string // Deprecated: Use parentApiId instead }) ``` ```ts // Delete a simple field client.deleteField({ apiId: 'title', parentApiId: 'Post', }); // Delete an enumerable field client.deleteField({ apiId: 'status', parentApiId: 'Post', }); // Delete a component field client.deleteField({ apiId: 'seoMetadata', parentApiId: 'Post', }); // Delete a component union field client.deleteField({ apiId: 'relatedPosts', parentApiId: 'Post', }); // Delete a relational field client.deleteField({ apiId: 'author', parentApiId: 'Post', }); // Delete a union field client.deleteField({ apiId: 'relatedPosts', parentApiId: 'Post', }); // Delete a remote field client.deleteField({ apiId: 'externalData', parentApiId: 'Post', }); // Delete a taxonomy field client.deleteField({ apiId: 'category', parentApiId: 'Post', }); console.log('Field deleted'); ``` ## Enumerations ### createEnumeration() Creates a new enumeration (enum) with its values. **Use cases:** - Creating status enums (`DRAFT`, `PUBLISHED`, `ARCHIVED`) - Setting up category types (`NEWS`, `BLOG`, `TUTORIAL`) - Defining content types (`ARTICLE`, `VIDEO`, `PODCAST`) - Creating priority levels (`LOW`, `MEDIUM`, `HIGH`, `URGENT`) **Important notes:** - Enumeration `apiId` must be PascalCase. - Enum values must be UPPER_SNAKE_CASE, such as, `DRAFT`, `PUBLISHED`. - Enumeration `apiId` and `displayName` must be unique within environment. - Enum values are referenced by their `apiId` in enumerable fields. ```ts client.createEnumeration({ apiId: string, // Required: Enumeration API ID displayName: string, // Required: Display name description?: string, // Optional: Description values: Array<{ // Required: Array of enum values apiId: string, // Required: Value API ID displayName: string // Required: Value display name }>, isSystem?: boolean // Optional: System enumeration flag (only for AppTokens) }) ``` ```ts // Basic enumeration with status values client.createEnumeration({ apiId: 'PostStatus', displayName: 'Post Status', description: 'Publication status for blog posts', values: [ { apiId: 'DRAFT', displayName: 'Draft' }, { apiId: 'REVIEW', displayName: 'In Review' }, { apiId: 'PUBLISHED', displayName: 'Published' }, ], }); // Priority enumeration client.createEnumeration({ apiId: 'Priority', displayName: 'Priority', description: 'Task priority levels', values: [ { apiId: 'LOW', displayName: 'Low' }, { apiId: 'MEDIUM', displayName: 'Medium' }, { apiId: 'HIGH', displayName: 'High' }, { apiId: 'URGENT', displayName: 'Urgent' }, ], }); ``` ### updateEnumeration() Updates an existing enumeration and its values. **Use cases:** - Adding new enum values. - Updating enum value display names. - Removing enum values. **Important notes:** - Enum values are managed via `valuesToAdd` and `valuesToRemove`. - Cannot change enum `apiId` directly (use `newApiId`). - Removing enum values may break existing entries using those values. ```ts client.updateEnumeration({ apiId: string, // Required: Current enumeration API ID newApiId?: string, // Optional: New API ID (to rename the enumeration) displayName?: string, // Optional: New display name description?: string, // Optional: New description valuesToCreate?: Array<{ // Optional: New enum values to add apiId: string, // Required: Value API ID displayName: string // Required: Value display name }>, valuesToUpdate?: Array<{ // Optional: Existing enum values to update apiId: string, // Required: Current value API ID newApiId?: string, // Optional: New value API ID (to rename) displayName?: string // Optional: New display name }>, valuesToDelete?: string[], // Optional: Array of value API IDs to delete isSystem?: boolean // Optional: System enumeration flag (only for AppTokens) }) ``` ```ts // Rename enumeration and update display name client.updateEnumeration({ apiId: 'PostStatus', newApiId: 'ArticleStatus', displayName: 'Article Status', description: 'Updated description', }); // Add new enum values client.updateEnumeration({ apiId: 'PostStatus', valuesToCreate: [ { apiId: 'ARCHIVED', displayName: 'Archived' }, { apiId: 'SCHEDULED', displayName: 'Scheduled' }, ], }); // Update existing enum values client.updateEnumeration({ apiId: 'PostStatus', valuesToUpdate: [ { apiId: 'REVIEW', newApiId: 'IN_REVIEW', displayName: 'In Review', }, ], }); // Delete enum values client.updateEnumeration({ apiId: 'PostStatus', valuesToDelete: ['ARCHIVED', 'SCHEDULED'], }); ``` ### deleteEnumeration() Deletes an enumeration. **Use cases:** - Removing unused enumerations. - Cleaning up deprecated enums. **Important notes:** - Permanent deletion. This action cannot be undone. - Cannot delete if used by enumerable fields. - Must delete all enumerable fields using the enumeration first. ```ts client.deleteEnumeration({ apiId: string // Required: The API ID of the enumeration to delete }) ``` ```ts // Delete an enumeration client.deleteEnumeration({ apiId: 'PostStatus', }); console.log('Enumeration deleted'); ``` ### createEnumerableField() Creates an enumerable field (enum field) on a model or component. **Use cases:** - Adding status fields to content models. - Creating category/type selection fields. - Setting up priority/rating fields. **Important notes:** - `enumerationApiId` must reference an existing enumeration. - `initialValue` must be the enum value `apiId` (not `displayName`). - Cannot use `isUnique` with `initialValue` or `migrationValue`. ```ts client.createEnumerableField({ apiId: string, // Required: Field API ID parentApiId: string, // Required: API ID of parent model/component (use instead of modelApiId) modelApiId?: string, // Deprecated: Use parentApiId instead enumerationApiId: string, // Required: API ID of the enumeration to use displayName: string, // Required: Display name description?: string, // Optional: Description isVariantEnabled?: boolean, // Optional: Enable variant support tableRenderer?: string, // Optional: Table renderer identifier formRenderer?: string, // Optional: Form renderer identifier tableExtension?: string, // Optional: Table extension identifier formExtension?: string, // Optional: Form extension identifier isList?: boolean, // Optional: Whether field accepts multiple values (default: false) // WARNING: Converting existing fields to/from list may cause data loss isLocalized?: boolean, // Optional: Whether field is localized (default: false) isRequired?: boolean, // Optional: Whether field is required (default: false) // When changing to true, provide migrationValue for existing null entries isUnique?: boolean, // Optional: Whether field is unique (default: false) // CANNOT be used with initialValue or migrationValue isHidden?: boolean, // Deprecated: Use visibility instead visibility?: VisibilityTypes, // Optional: Visibility setting isTitle?: boolean, // Optional: Whether field is used as title (default: false) position?: number, // Optional: Field position migrationValue?: string, // Optional: Migration value for existing data (enum value API ID) initialValue?: string, // Optional: Default value (enum value API ID, stringified) visibilityCondition?: { // Optional: Show/hide field based on another field's value baseField: string, // Required: API ID of the field that controls visibility operator: FieldConditionOperator, // Required: Comparison operator (IS, IS_NOT, etc.) enumerationValues?: string[], // Required when baseField is enumeration type booleanValue?: boolean // Required when baseField is boolean type }, isSystem?: boolean // Optional: System field flag (only for AppTokens) }) ``` ```ts // Basic enumerable field client.createEnumerableField({ parentApiId: 'Post', apiId: 'status', displayName: 'Status', enumerationApiId: 'PostStatus', isRequired: true, initialValue: 'DRAFT', // Enum value API ID }); // Enumerable field with list support client.createEnumerableField({ parentApiId: 'Post', apiId: 'tags', displayName: 'Tags', enumerationApiId: 'PostTag', isList: true, isRequired: false, }); ``` **Enums** - `VisibilityTypes`: `READ_WRITE`, `READ_ONLY`, `HIDDEN`, `API_ONLY` - `FieldConditionOperator`: `IS`, `IS_NOT`, `CONTAINS_ANY`, `CONTAINS_ALL`, `CONTAINS_NONE` ### updateEnumerableField() Updates an existing enumerable field. **Use cases:** - Changing enumeration reference - Updating field visibility - Adding conditional visibility **Important notes:** - Can change `enumerationApiId` to reference a different enumeration. - Must ensure new enumeration has compatible values. ```ts client.updateEnumerableField({ apiId: string, // Required: Current field API ID newApiId?: string, // Optional: New API ID (to rename the field) parentApiId?: string, // Optional: Parent model/component API ID displayName?: string, // Optional: New display name description?: string, // Optional: New description isVariantEnabled?: boolean, // Optional: Enable/disable variant support isList?: boolean, // Optional: Whether field accepts multiple values (default: false) // WARNING: Converting existing fields to/from list may cause data loss isLocalized?: boolean, // Optional: Whether field is localized isRequired?: boolean, // Optional: Whether field is required // When changing to true, provide migrationValue for existing null entries isUnique?: boolean, // Optional: Whether field is unique // CANNOT be used with initialValue or migrationValue isHidden?: boolean, // Deprecated: Use visibility instead visibility?: VisibilityTypes, // Optional: Visibility setting isTitle?: boolean, // Optional: Whether field is used as title position?: number, // Optional: Field position initialValue?: string, // Optional: Default value (enum value API ID, stringified) migrationValue?: string, // Optional: Migration value for existing data visibilityCondition?: { // Optional: Show/hide field based on another field's value baseField: string, // Required: API ID of the field that controls visibility operator: FieldConditionOperator, // Required: Comparison operator (IS, IS_NOT, etc.) enumerationValues?: string[], // Required when baseField is enumeration type booleanValue?: boolean // Required when baseField is boolean type }, isSystem?: boolean // Optional: System field flag (only for AppTokens) }) ``` ```ts // Basic update - rename field and change display name client.updateEnumerableField({ apiId: 'status', newApiId: 'publicationStatus', displayName: 'Publication Status', description: 'Current publication status', }); // Update initial value client.updateEnumerableField({ apiId: 'status', initialValue: 'PUBLISHED', // Enum value API ID }); // Update visibility client.updateEnumerableField({ apiId: 'status', visibility: VisibilityTypes.READ_ONLY, }); ``` **Enums** - `VisibilityTypes`: `READ_WRITE`, `READ_ONLY`, `HIDDEN`, `API_ONLY` - `FieldConditionOperator`: `IS`, `IS_NOT`, `CONTAINS_ANY`, `CONTAINS_ALL`, `CONTAINS_NONE` ## Components ### createComponent() Creates a new component. **Use cases:** - Creating reusable field groups (`SEO metadata`, `contact info`, `address`). - Building modular content blocks (`CTA`, `image block`, `video block`). - Setting up shared field structures across multiple models. **Important notes:** - Component `apiId` must be PascalCase. - `apiId` and `apiIdPlural` must be different. - Components can be nested (components can contain other components). - Components cannot have required fields if used in multiple places. ```ts client.createComponent({ apiId: string, // Required: Component API ID apiIdPlural: string, // Required: Plural API ID (used for lists) displayName: string, // Required: Display name shown in the webapp description?: string // Optional: Description of the component }) ``` ```ts // Basic component creation client.createComponent({ apiId: 'SeoMetadata', apiIdPlural: 'SeoMetadatas', displayName: 'SEO Metadata', description: 'Search engine optimization fields', }); // Contact details component client.createComponent({ apiId: 'ContactDetails', apiIdPlural: 'ContactDetailsCollection', displayName: 'Contact Details', description: 'Phone and email contact information', }); ``` ### updateComponent() Updates an existing component. **Use cases:** - Renaming components. - Updating descriptions. - Managing component fields. **Important notes:** - Renaming updates all references to the component. - Cannot change `apiId` directly (use `newApiId`). ```ts client.updateComponent({ apiId: string, // Required: Current component API ID newApiId?: string, // Optional: New API ID (to rename the component) apiIdPlural?: string, // Optional: New plural API ID displayName?: string, // Optional: New display name description?: string // Optional: New description }) ``` ```ts // Basic update - rename component and change display name client.updateComponent({ apiId: 'SeoMetadata', newApiId: 'SeoMeta', displayName: 'SEO Meta', description: 'Updated SEO metadata fields', }); // Update only display name client.updateComponent({ apiId: 'ContactDetails', displayName: 'Contact Information', }); // Update plural API ID client.updateComponent({ apiId: 'AuthorInfo', apiIdPlural: 'AuthorInfoCollection', }); ``` ### deleteComponent() Deletes a component. **Use cases:** - Removing unused components. - Cleaning up deprecated components. **Important notes:** - Permanent deletion. This action cannot be undone. - Cannot delete if used by any models or components. - Must delete all component fields using the component first ```ts client.deleteComponent({ apiId: string // Required: The API ID of the component to delete }) ``` ```ts // Delete a component client.deleteComponent({ apiId: 'SeoMetadata', }); console.log('Component deleted'); ``` ### createComponentField() Creates a component field that embeds a component into a model or another component. **Use cases:** - Embedding reusable components into models. - Adding SEO metadata to content models. - Including contact information components. **Important notes:** - `parentApiId` can be a model or another component (for nesting). - `componentApiId` references the component to embed. - `isList: true` allows multiple instances of the component. ```ts client.createComponentField({ apiId: string, // Required: Field API ID parentApiId: string, // Required: API ID of parent model/component componentApiId: string, // Required: API ID of the component to embed displayName: string, // Required: Display name description?: string, // Optional: Description isVariantEnabled?: boolean, // Optional: Enable variant support tableRenderer?: string, // Optional: Table renderer identifier formRenderer?: string, // Optional: Form renderer identifier tableExtension?: string, // Optional: Table extension identifier formExtension?: string, // Optional: Form extension identifier isList?: boolean, // Optional: Whether field accepts multiple values (default: false) // WARNING: Converting existing fields to/from list may cause data loss isRequired?: boolean, // Optional: Whether field is required (default: false) // When changing to true, provide migrationValue for existing null entries visibility?: VisibilityTypes, // Optional: Visibility setting position?: number, // Optional: Field position visibilityCondition?: { // Optional: Show/hide field based on another field's value baseField: string, // Required: API ID of the field that controls visibility operator: FieldConditionOperator, // Required: Comparison operator (IS, IS_NOT, etc.) enumerationValues?: string[], // Required when baseField is enumeration type booleanValue?: boolean // Required when baseField is boolean type }, }) ``` ```ts // Basic component field - embed SEO component in Post model client.createComponentField({ parentApiId: 'Post', apiId: 'seo', displayName: 'SEO', componentApiId: 'SeoMetadata', isRequired: false, }); // Component field as a list client.createComponentField({ parentApiId: 'Post', apiId: 'contentBlocks', displayName: 'Content Blocks', componentApiId: 'ImageBlock', isList: true, isRequired: false, }); // Nested component - embed component inside another component client.createComponentField({ parentApiId: 'AuthorInfo', // Component API ID apiId: 'contact', displayName: 'Contact', componentApiId: 'ContactDetails', // Another component isRequired: false, }); // Create basic component field client.createComponentField({ parentApiId: 'Post', apiId: 'seo', displayName: 'SEO', description: 'Search engine optimization metadata', componentApiId: 'SeoMetadata', }); ``` **Enums** - `VisibilityTypes`: `READ_WRITE`, `READ_ONLY`, `HIDDEN`, `API_ONLY` - `FieldConditionOperator`: `IS`, `IS_NOT`, `CONTAINS_ANY`, `CONTAINS_ALL`, `CONTAINS_NONE` ### updateComponentField() Updates an existing component field. **Use cases:** - Renaming component fields. - Changing component reference - Updating field visibility - Adding conditional visibility. - Changing required status. **Important notes:** - Can change `componentApiId` to reference a different component. - Cannot change `isList` without potential data loss. - When changing `isRequired` to `true`, must provide `migrationValue` for existing null entries. - `visibilityCondition` can be set to `null` to remove it. ```ts client.updateComponentField({ apiId: string, // Required: Current field API ID parentApiId: string, // Required: Parent model/component API ID newApiId?: string, // Optional: New API ID (to rename the field) displayName?: string, // Optional: New display name description?: string, // Optional: New description isList?: boolean, // Optional: Whether field accepts multiple values (default: false) // WARNING: Converting existing fields to/from list may cause data loss isRequired?: boolean, // Optional: Whether field is required // When changing to true, provide migrationValue for existing null entries visibility?: VisibilityTypes, // Optional: Visibility setting isVariantEnabled?: boolean, // Optional: Enable/disable variant support position?: number, // Optional: Field position visibilityCondition?: { // Optional: Show/hide field based on another field's value baseField: string, // Required: API ID of the field that controls visibility operator: FieldConditionOperator, // Required: Comparison operator (IS, IS_NOT, etc.) enumerationValues?: string[], // Required when baseField is enumeration type booleanValue?: boolean // Required when baseField is boolean type }, }) ``` ```ts // Basic update - rename field and change display name client.updateComponentField({ apiId: 'seo', parentApiId: 'Post', newApiId: 'seoMetadata', displayName: 'SEO Metadata', description: 'Search engine optimization fields', }); // Update to make field required client.updateComponentField({ apiId: 'seo', parentApiId: 'Post', isRequired: true, }); // Update visibility client.updateComponentField({ apiId: 'seo', parentApiId: 'Post', visibility: VisibilityTypes.READ_ONLY, }); ``` **Enums** - `VisibilityTypes`: `READ_WRITE`, `READ_ONLY`, `HIDDEN`, `API_ONLY` - `FieldConditionOperator`: `IS`, `IS_NOT`, `CONTAINS_ANY`, `CONTAINS_ALL`, `CONTAINS_NONE` ### createComponentUnionField() Creates a modular component field that allows editors to choose from multiple component types. **Use cases:** - Building flexible page builders. - Creating modular content systems. - Allowing content editors to choose from multiple component types. **Important notes:** - `componentApiIds` must contain at least one component. - Allows selecting from multiple component types in a single field. - Useful for flexible, modular content structures. - Content editors choose which component type to use per entry. ```ts client.createComponentUnionField({ // Required parameters apiId: string, // Field API ID (camelCase) parentApiId: string, // API ID of parent model (PascalCase) displayName: string, // Display name shown in UI componentApiIds: string[], // Array of component API IDs (PascalCase) - at least one required // Optional parameters description?: string, // Description text isVariantEnabled?: boolean, // Enable variant support tableRenderer?: string, // Table renderer identifier formRenderer?: string, // Form renderer identifier tableExtension?: string, // Table extension identifier formExtension?: string, // Form extension identifier isList?: boolean, // Whether field accepts multiple values (default: false) // WARNING: Converting existing fields to/from list may cause data loss isRequired?: boolean, // Whether field is required (default: false) visibility?: VisibilityTypes, // Visibility setting visibilityCondition?: { // Conditional visibility baseField: string, // API ID of the field that controls visibility operator: FieldConditionOperator, // Comparison operator enumerationValues?: string[], // Required when baseField is enumeration type booleanValue?: boolean // Required when baseField is boolean type }, position?: number // Field position }) ``` ```ts // Complete example - creating components and union field // 1. Create individual components client.createComponent({ apiId: 'CallToAction', apiIdPlural: 'CallToActions', displayName: 'Call to Action', description: 'CTA block with button', }); client.createComponent({ apiId: 'ImageBlock', apiIdPlural: 'ImageBlocks', displayName: 'Image Block', description: 'Image with caption', }); client.createComponent({ apiId: 'VideoBlock', apiIdPlural: 'VideoBlocks', displayName: 'Video Block', description: 'Video embed block', }); // 2. Add fields to components client.createSimpleField({ parentApiId: 'CallToAction', type: SimpleFieldType.STRING, apiId: 'heading', displayName: 'Heading', isRequired: true, }); client.createSimpleField({ parentApiId: 'ImageBlock', type: SimpleFieldType.STRING, apiId: 'imageUrl', displayName: 'Image URL', isRequired: true, }); // 3. Create the union field that allows any of these components client.createComponentUnionField({ parentApiId: 'Post', apiId: 'contentBlocks', displayName: 'Content Blocks', description: 'Flexible content blocks for rich posts', componentApiIds: ['CallToAction', 'ImageBlock', 'VideoBlock'], isList: true, isRequired: false, }); // Landing page sections (conditional visibility) client.createComponentUnionField({ parentApiId: 'Post', apiId: 'contentBlocks', displayName: 'Content Blocks', componentApiIds: ['CallToAction', 'ImageBlock'], visibilityCondition: { baseField: 'status', operator: FieldConditionOperator.IS, enumerationValues: ['PUBLISHED'], }, }); ``` **Enums** - `VisibilityTypes`: `READ_WRITE`, `READ_ONLY`, `HIDDEN`, `API_ONLY` - `FieldConditionOperator`: `IS`, `IS_NOT`, `CONTAINS_ANY`, `CONTAINS_ALL`, `CONTAINS_NONE` ### updateComponentUnionField() Updates a modular component field. **Use cases:** - Adding new component types to union. - Removing component types from union. - Updating which components are available. **Important notes:** - `componentApiIds` replaces the entire list. - To remove a component, provide complete list WITHOUT it. - To add a component, provide complete list INCLUDING it. - Cannot change `isList` or `isRequired` (not available in update) ```ts client.updateComponentUnionField({ // Required parameters apiId: string, // Current field API ID parentApiId: string, // Parent model API ID // Optional parameters newApiId?: string, // New API ID (to rename the field) displayName?: string, // New display name description?: string, // New description isVariantEnabled?: boolean, // Enable/disable variant support componentApiIds?: string[], // Array of component API IDs - REPLACES entire list // To remove a component, provide the complete list WITHOUT it visibilityCondition?: { // Conditional visibility baseField: string, // API ID of the field that controls visibility operator: FieldConditionOperator, // Comparison operator enumerationValues?: string[], // Required when baseField is enumeration type booleanValue?: boolean // Required when baseField is boolean type }, position?: number // Field position }) ``` ```ts // Basic update - rename field client.updateComponentUnionField({ apiId: 'contentBlocks', parentApiId: 'Post', newApiId: 'postContentBlocks', displayName: 'Post Content Blocks', }); // Remove component from a component union field client.updateComponentUnionField({ apiId: '', parentApiId: '', componentApiIds: [ '', // VideoBlock removed - only include components you want to keep ], }); // Add a component to the union // Original: ['CallToAction', 'ImageBlock'. Add VideoBlock - provide complete list INCLUDING VideoBlock client.updateComponentUnionField({ apiId: 'contentBlocks', parentApiId: 'Post', componentApiIds: [ 'CallToAction', 'ImageBlock', 'VideoBlock', // Added ], }); // Update multiple properties client.updateComponentUnionField({ apiId: 'contentBlocks', parentApiId: 'Post', newApiId: 'sections', displayName: 'Page Sections', description: 'Flexible page sections', componentApiIds: ['HeroSection', 'FeaturesSection', 'TestimonialsSection'], }); // Add conditional visibility client.updateComponentUnionField({ apiId: 'contentBlocks', parentApiId: 'Post', visibilityCondition: { baseField: 'status', operator: FieldConditionOperator.IS, enumerationValues: ['PUBLISHED'], }, }); // Remove conditional visibility client.updateComponentUnionField({ apiId: 'contentBlocks', parentApiId: 'Post', visibilityCondition: null, // Removes the visibility condition }); // Modify component list // Original union field with: ['CallToAction', 'ImageBlock', 'VideoBlock'] // Remove VideoBlock and add QuoteBlock client.updateComponentUnionField({ apiId: 'contentBlocks', parentApiId: 'Post', componentApiIds: [ 'CallToAction', 'ImageBlock', 'QuoteBlock', // Added // VideoBlock removed ], }); ``` **Enums** - `FieldConditionOperator`: `IS`, `IS_NOT`, `CONTAINS_ANY`, `CONTAINS_ALL`, `CONTAINS_NONE` ## Relational fields ### createRelationalField() Creates a relational field that links models together (relation) or to assets (asset). **Use cases:** - Linking models together (`Post → Author`, `Product → Category`). - Connecting content to assets (`Post → Featured Image`). - Creating many-to-many relations (`Post ↔ Category`). - Setting up one-to-many relations (`Author → Posts`). **Important notes:** - Cardinality is immutable. It is set at creation and cannot be changed later. - `isRequired: true` is ONLY supported for ASSET type, not RELATION type. - `reverseField.modelApiId` is the TARGET model being referenced. - For unidirectional relations, `reverseField.position` should be negative. - For components, `reverseField.isList` must be true to allow reconnection. Relation cardinality is set at creation and **cannot be changed later**. You cannot convert: - One-to-one → One-to-many - One-to-many → Many-to-many - Any other cardinality changes If you need a different cardinality, you must delete and recreate the relation (which may cause data loss). **For components and unidirectional relations:** The reverse side must have `isList: true` to allow reconnecting entries to the same component instance. Setting `isList: false` will prevent reconnection and may require recreating the relation. ```ts client.createRelationalField({ apiId: string, // Required: Field API ID parentApiId: string, // Required: API ID of parent model (use instead of modelApiId) modelApiId?: string, // Deprecated: Use parentApiId instead type: RelationalFieldType, // Required: RELATION or ASSET displayName: string, // Required: Display name description?: string, // Optional: Description isVariantEnabled?: boolean, // Optional: Enable variant support tableRenderer?: string, // Optional: Table renderer identifier formRenderer?: string, // Optional: Form renderer identifier tableExtension?: string, // Optional: Table extension identifier formExtension?: string, // Optional: Form extension identifier isList?: boolean, // Optional: Whether THIS side accepts multiple values (default: false) // Combined with reverseField.isList determines cardinality: // - false + reverse false = one-to-one (this is parent) // - false + reverse true = one-to-many (this is child) // - true + reverse false = many-to-one (this is parent) // - true + reverse true = many-to-many // CANNOT be changed after creation! isRequired?: boolean, // Optional: Whether field is required // ONLY valid for ASSET type. Regular RELATION fields cannot be required isHidden?: boolean, // Deprecated: Use visibility instead visibility?: VisibilityTypes, // Optional: Visibility setting formConfig?: JSON, // Optional: Form configuration JSON tableConfig?: JSON, // Optional: Table configuration JSON reverseField: { // Required: Configuration for the reverse side of the relation apiId: string, // Required: API ID for reverse field (appears on related model) modelApiId: string, // Required: API ID of the related/target model displayName: string, // Required: Display name for reverse field description?: string, // Optional: Description for reverse field isList?: boolean, // Optional: Whether reverse side accepts multiple values // IMPORTANT for components: Must be true to allow reconnection! isHidden?: boolean, // Deprecated: Use visibility instead visibility?: VisibilityTypes, // Optional: Visibility of reverse field isUnidirectional?: boolean, // Optional: Create one-way relation (no reverse field shown in UI) // When true, related model won't display this relation position?: number // Optional: Position of reverse field // MUST be negative for unidirectional relations }, visibilityCondition?: { // Optional: Show/hide field based on another field's value baseField: string, // Required: API ID of the field that controls visibility operator: FieldConditionOperator, // Required: Comparison operator (IS, IS_NOT, etc.) enumerationValues?: string[], // Required when baseField is enumeration type booleanValue?: boolean // Required when baseField is boolean type }, position?: number // Optional: Reverse field position // MUST be negative for unidirectional relations }) ``` ```ts // Create a required uni-directional asset field client.createRelationalField({ parentApiId: 'Post', apiId: 'featuredImage', displayName: 'Featured Image', type: RelationalFieldType.ASSET, isRequired: true, reverseField: { isUnidirectional: true, apiId: 'featuredInPosts', displayName: 'Featured In Posts', modelApiId: 'Asset', }, }); // Create a many-to-many (M-N) relation client.createRelationalField({ parentApiId: 'Post', apiId: 'categories', displayName: 'Categories', type: RelationalFieldType.RELATION, isList: true, reverseField: { modelApiId: 'Category', apiId: 'posts', displayName: 'Posts', isList: true, }, }); // Basic relation - Post to Author (many-to-one) client.createRelationalField({ parentApiId: 'Post', apiId: 'author', displayName: 'Author', type: RelationalFieldType.RELATION, reverseField: { apiId: 'posts', modelApiId: 'Author', displayName: 'Posts', isList: true, }, }); // Asset relation - Post to featured image client.createRelationalField({ parentApiId: 'Post', apiId: 'featuredImage', displayName: 'Featured Image', type: RelationalFieldType.ASSET, isRequired: true, // isRequired is only supported for ASSET type reverseField: { apiId: 'featuredInPosts', modelApiId: 'Asset', displayName: 'Featured In Posts', isList: true, }, }); ``` **Enums** - `RelationalFieldType`: `RELATION`, `ASSET` - `VisibilityTypes`: `READ_WRITE`, `READ_ONLY`, `HIDDEN`, `API_ONLY` - `FieldConditionOperator`: `IS`, `IS_NOT`, `CONTAINS_ANY`, `CONTAINS_ALL`, `CONTAINS_NONE` ### updateRelationalField() Updates an existing relational field. **Use cases:** - Making relations unidirectional. - Updating field visibility. - Changing required status (for `ASSET` type only). **Important notes:** - Cannot change cardinality (`isList` cannot be changed). - `isRequired` can only be changed for `ASSET` type. - `isUnidirectional` is a top-level parameter (not in `reverseField`). - No `reverseField` parameter in update (the reverse field is managed separately). ```ts client.updateRelationalField({ apiId: string, // Required: Current field API ID newApiId?: string, // Optional: New API ID (to rename the field) parentApiId?: string, // Optional: Parent model API ID displayName?: string, // Optional: New display name description?: string, // Optional: New description isVariantEnabled?: boolean, // Optional: Enable/disable variant support isList?: boolean, // Optional: Whether field accepts multiple values (default: false) // WARNING: Converting existing fields to/from list may cause data loss isRequired?: boolean, // Optional: Whether field is required (only for ASSET type!) // When changing to true, provide migrationValue for existing null entries isHidden?: boolean, // Deprecated: Use visibility instead visibility?: VisibilityTypes, // Optional: Visibility setting isUnidirectional?: boolean, // Optional: Make relation unidirectional formConfig?: JSON, // Optional: Form configuration JSON tableConfig?: JSON, // Optional: Table configuration JSON visibilityCondition?: { // Optional: Show/hide field based on another field's value baseField: string, // Required: API ID of the field that controls visibility operator: FieldConditionOperator, // Required: Comparison operator (IS, IS_NOT, etc.) enumerationValues?: string[], // Required when baseField is enumeration type booleanValue?: boolean // Required when baseField is boolean type }, position?: number // Optional: Field position }) ``` ```ts // Basic update - rename field and change display name client.updateRelationalField({ apiId: 'author', parentApiId: 'Post', newApiId: 'postAuthor', displayName: 'Post Author', description: 'Author of the post', }); // Update to make field required (only for ASSET type) client.updateRelationalField({ apiId: 'featuredImage', parentApiId: 'Post', isRequired: true, // Only works for ASSET type }); // Make relation unidirectional client.updateRelationalField({ apiId: 'relatedPosts', parentApiId: 'Post', isUnidirectional: true, }); ``` **Enums** - `VisibilityTypes`: `READ_WRITE`, `READ_ONLY`, `HIDDEN`, `API_ONLY` - `FieldConditionOperator`: `IS`, `IS_NOT`, `CONTAINS_ANY`, `CONTAINS_ALL`, `CONTAINS_NONE` ## Union fields ### createUnionField() Creates a union field that links to multiple models (not components). **Use cases:** - Creating polymorphic relationships (`Post` can reference `Author` OR `Organization`). - Allowing flexible model references. - Building content that can link to multiple model types. **Important notes:** - `modelApiIds` must contain at least one model API ID. - Allows selecting from multiple model types in a single field. - `reverseField` is required and must specify `modelApiIds` array. - Useful for polymorphic content relationships. ```ts client.createUnionField({ apiId: string, // Required: Field API ID parentApiId: string, // Required: API ID of parent model (use instead of modelApiId) modelApiId?: string, // Deprecated: Use parentApiId instead displayName: string, // Required: Display name description?: string, // Optional: Description isVariantEnabled?: boolean, // Optional: Enable variant support tableRenderer?: string, // Optional: Table renderer identifier formRenderer?: string, // Optional: Form renderer identifier tableExtension?: string, // Optional: Table extension identifier formExtension?: string, // Optional: Form extension identifier isList?: boolean, // Optional: Whether field accepts multiple values (default: false) // WARNING: Converting existing fields to/from list may cause data loss isHidden?: boolean, // Deprecated: Use visibility instead visibility?: VisibilityTypes, // Optional: Visibility setting reverseField: { // Required: Reverse field configuration apiId?: string, // Optional: Reverse field API ID modelApiIds: string[], // Required: Complete array of model API IDs // NOTE: Replaces entire list (not merged with existing!) // Must include all models you want to keep displayName?: string, // Optional: Reverse field display name description?: string, // Optional: Reverse field description isList?: boolean, // Optional: Whether field accepts multiple values (default: false) // WARNING: Converting existing fields to/from list may cause data loss isHidden?: boolean, // Deprecated: Use visibility instead visibility?: VisibilityTypes, // Optional: Reverse field visibility positions?: Array<{ // Optional: Position for each model modelApiId: string, // Required: Model API ID position: number // Required: Display osition }> }, visibilityCondition?: { // Optional: Show/hide field based on another field's value baseField: string, // Required: API ID of the field that controls visibility operator: FieldConditionOperator, // Required: Comparison operator (IS, IS_NOT, etc.) enumerationValues?: string[], // Required when baseField is enumeration type booleanValue?: boolean // Required when baseField is boolean type }, position?: number // Optional: Field position }) ``` ```ts // Basic union field - link to multiple models client.createUnionField({ parentApiId: 'Post', apiId: 'authorOrEditor', displayName: 'Author or Editor', reverseField: { modelApiIds: ['Author', 'Editor'], displayName: 'Posts', isList: true, }, }); // Union field with list support client.createUnionField({ parentApiId: 'Post', apiId: 'relatedContent', displayName: 'Related Content', isList: true, reverseField: { modelApiIds: ['Post', 'Page', 'Video'], displayName: 'Related From', isList: true, }, }); ``` **Enums** - `VisibilityTypes`: `READ_WRITE`, `READ_ONLY`, `HIDDEN`, `API_ONLY` - `FieldConditionOperator`: `IS`, `IS_NOT`, `CONTAINS_ANY`, `CONTAINS_ALL`, `CONTAINS_NONE` ### updateUnionField() Updates an existing union field. **Use cases:** - Adding or removing model types from union. - Updating reverse field configuration. **Important notes:** - `modelApiIds` replaces the entire list. - `reverseField.modelApiIds` can be updated. - Cannot change entire `reverseField` structure. ```ts client.updateUnionField({ apiId: string, // Required: Current field API ID newApiId?: string, // Optional: New API ID (to rename the field) parentApiId?: string, // Optional: Parent model API ID displayName?: string, // Optional: New display name description?: string, // Optional: New description visibility?: VisibilityTypes, // Optional: Visibility setting isVariantEnabled?: boolean, // Optional: Enable/disable variant support position?: number, // Optional: Field position reverseField?: { // Optional: Update reverse field configuration modelApiIds: string[], // Required: Array of model API IDs (complete list) positions?: Array<{ // Optional: Position for each model modelApiId: string, // Required: Model API ID position: number // Required: Position value }> }, visibilityCondition?: { // Optional: Show/hide field based on another field's value baseField: string, // Required: API ID of the field that controls visibility operator: FieldConditionOperator, // Required: Comparison operator (IS, IS_NOT, etc.) enumerationValues?: string[], // Required when baseField is enumeration type booleanValue?: boolean // Required when baseField is boolean type }, }) ``` ```ts // Basic update - rename field and change display name client.updateUnionField({ apiId: 'authorOrEditor', parentApiId: 'Post', newApiId: 'contentAuthor', displayName: 'Content Author', description: 'Author or editor of the content', }); // Update reverse field - change which models can be linked client.updateUnionField({ apiId: 'authorOrEditor', parentApiId: 'Post', reverseField: { modelApiIds: ['Author', 'Editor', 'Contributor'], // Complete list of models }, }); // Remove a model from the union (provide only models you want to keep) client.updateUnionField({ apiId: 'authorOrEditor', parentApiId: 'Post', reverseField: { modelApiIds: ['Author'], // Only Author remains, Editor is removed }, }); ``` **Enums** - `VisibilityTypes`: `READ_WRITE`, `READ_ONLY`, `HIDDEN`, `API_ONLY` - `FieldConditionOperator`: `IS`, `IS_NOT`, `CONTAINS_ANY`, `CONTAINS_ALL`, `CONTAINS_NONE` ## Remote sources ### createGraphQLRemoteSource() Creates a new GraphQL remote source. **Use cases:** - Integrating external GraphQL APIs. - Connecting to commerce platforms (CommerceTools, CommerceLayer) - Building federated content systems. **Important notes:** - `prefix` is immutable. It cannot be changed after creation. - `prefix` is prepended to all remote types. - `introspectionUrl` can differ from `url` if introspection is on different endpoint. - `remoteTypeDefinitions` allows custom GraphQL types. - `OAuth` configuration is optional but required for protected APIs. ```ts client.createGraphQLRemoteSource({ displayName: string, // Required: Display name prefix: string, // Required: Unique prefix for remote types (cannot be changed!) url: string, // Required: GraphQL endpoint URL kind: RemoteSourceKind, // Required: Remote source kind enum introspectionMethod: GraphQLRemoteSourceIntrospectionMethod, // Required: HTTP method for introspection description?: string, // Optional: Description headers?: JSON, // Optional: Custom headers (JSON object) introspectionUrl?: string, // Optional: Separate URL for introspection (if different from url) introspectionHeaders?: JSON, // Optional: Headers for introspection (JSON object) remoteTypeDefinitions?: { // Optional: Custom GraphQL type definitions sdl: string // Required: GraphQL SDL string }, debugEnabled?: boolean, // Optional: Enable debug mode oAuth?: { // Optional: OAuth configuration clientId: string, // Required: OAuth client ID clientSecret?: string, // Optional: OAuth client secret scopes?: string[], // Optional: OAuth scopes authorizationGrantType: OAuthGrantType, // Required: OAuth grant type authorizationUrl: string // Required: OAuth authorization URL } }) ``` ```ts // Basic GraphQL remote source client.createGraphQLRemoteSource({ displayName: 'External GraphQL API', prefix: 'External', url: 'https://api.example.com/graphql', kind: RemoteSourceKind.Custom, introspectionMethod: GraphQLRemoteSourceIntrospectionMethod.POST, }); // GraphQL remote source with headers client.createGraphQLRemoteSource({ displayName: 'Protected GraphQL API', prefix: 'Protected', url: 'https://api.example.com/graphql', kind: RemoteSourceKind.Custom, introspectionMethod: GraphQLRemoteSourceIntrospectionMethod.POST, headers: { Authorization: 'Bearer api-token', 'X-API-Key': 'your-api-key', }, }); // GraphQL remote source with OAuth client.createGraphQLRemoteSource({ displayName: 'OAuth GraphQL API', prefix: 'OAuth', url: 'https://api.example.com/graphql', kind: RemoteSourceKind.Custom, introspectionMethod: GraphQLRemoteSourceIntrospectionMethod.POST, oAuth: { clientId: 'your-client-id', clientSecret: 'your-client-secret', scopes: ['read:products', 'write:products'], authorizationGrantType: OAuthGrantType.client_credentials, authorizationUrl: 'https://auth.example.com/oauth/token', }, }); ``` **Enums** - `RemoteSourceKind`: `CommerceTools`, `CommerceLayer`, `Custom` - `GraphQLRemoteSourceIntrospectionMethod`: `GET`, `POST` - `OAuthGrantType`: `client_credentials` ### updateGraphQLRemoteSource() Updates an existing GraphQL remote source. **Use cases:** - Updating remote source URLs. - Changing authentication headers. - Modifying OAuth configuration. - Adding/updating remote type definitions. - Updating introspection settings. **Important notes:** - `prefix` cannot be changed (immutable). - `remoteTypeDefinitionsToUpsert` uses create/update/delete structure. - OAuth configuration can be updated. - `introspectionUrl` and `introspectionMethod` can be changed. - Headers can be updated for authentication. ```ts client.updateGraphQLRemoteSource({ prefix: string, // Required: Remote source prefix (used to identify the source) displayName?: string, // Optional: New display name description?: string, // Optional: New description url?: string, // Optional: New GraphQL endpoint URL headers?: JSON, // Optional: Custom headers (JSON object) introspectionUrl?: string, // Optional: Separate URL for introspection introspectionMethod?: GraphQLRemoteSourceIntrospectionMethod, // Optional: HTTP method for introspection introspectionHeaders?: JSON, // Optional: Headers for introspection (JSON object) remoteTypeDefinitionsToUpsert?: { // Optional: Manage remote type definitions remoteTypeDefinitionsToCreate?: Array<{ // Optional: Create new type definitions sdl: string // Required: GraphQL SDL string }>, remoteTypeDefinitionsToUpdate?: Array<{ // Optional: Update existing type definitions apiId: string, // Required: Type definition API ID sdl?: string // Optional: New SDL string }>, remoteTypeDefinitionsToDelete?: Array<{ // Optional: Delete type definitions apiId: string // Required: Type definition API ID }> }, debugEnabled?: boolean, // Optional: Enable/disable debug mode kind?: RemoteSourceKind, // Optional: Remote source kind enum oAuth?: { // Optional: OAuth configuration clientId: string, // Required: OAuth client ID clientSecret?: string, // Optional: OAuth client secret scopes?: string[], // Optional: OAuth scopes authorizationGrantType: OAuthGrantType, // Required: OAuth grant type authorizationUrl: string // Required: OAuth authorization URL } }) ``` ```ts // Basic update - change display name and URL client.updateGraphQLRemoteSource({ prefix: 'External', displayName: 'Updated GraphQL API', url: 'https://api.example.com/new-graphql', }); // Update headers client.updateGraphQLRemoteSource({ prefix: 'External', headers: { Authorization: 'Bearer new-token', 'X-API-Key': 'new-api-key', }, }); // Add new remote type definitions client.updateGraphQLRemoteSource({ prefix: 'External', remoteTypeDefinitionsToUpsert: { remoteTypeDefinitionsToCreate: [ { sdl: ` input NewFilterInput { field: String! value: String! } `, }, ], }, }); ``` **Enums** - `RemoteSourceKind`: `CommerceTools`, `CommerceLayer`, `Custom` - `GraphQLRemoteSourceIntrospectionMethod`: `GET`, `POST` - `OAuthGrantType`: `client_credentials` ### refreshGraphQLRemoteSourceSchema() Refreshes the schema for a GraphQL remote source. **Use cases:** - Updating remote schema after API changes. - Syncing with latest remote API schema. - Refreshing available types and fields. **Important notes:** - Only requires prefix to identify the remote source. - Async operation. It may take time to complete. - Updates available types and fields from remote schema. - Should be called when remote API schema changes. ```ts client.refreshGraphQLRemoteSourceSchema({ prefix: string // Required: Remote source prefix }) ``` ```ts // Refresh schema for a GraphQL remote source client.refreshGraphQLRemoteSourceSchema({ prefix: 'External', }); // Refresh schema after updating the remote API client.refreshGraphQLRemoteSourceSchema({ prefix: 'CommerceTools', }); ``` ### createRESTRemoteSource() Creates a new REST remote source. **Use cases:** - Integrating REST APIs. - Connecting to external REST services. - Building API integrations. **Important notes:** - `prefix` is immutable. It cannot be changed after creation. - `remoteTypeDefinitions` define available types and operations. - `OAuth` configuration is optional but required for protected APIs. ```ts client.createRESTRemoteSource({ displayName: string, // Required: Display name prefix: string, // Required: Unique prefix for remote types (cannot be changed!) url: string, // Required: REST API base URL kind: RemoteSourceKind, // Required: Remote source kind enum description?: string, // Optional: Description headers?: JSON, // Optional: Custom headers (JSON object) remoteTypeDefinitions?: { // Optional: Remote type definitions sdl: string // Required: GraphQL SDL string defining types }, debugEnabled?: boolean, // Optional: Enable debug mode oAuth?: { // Optional: OAuth configuration clientId: string, // Required: OAuth client ID clientSecret?: string, // Optional: OAuth client secret scopes?: string[], // Optional: OAuth scopes authorizationGrantType: OAuthGrantType, // Required: OAuth grant type authorizationUrl: string // Required: OAuth authorization URL } }) ``` ```ts // Basic REST remote source client.createRESTRemoteSource({ displayName: 'External REST API', prefix: 'External', url: 'https://api.example.com', kind: RemoteSourceKind.Custom, }); // REST remote source with headers client.createRESTRemoteSource({ displayName: 'Protected REST API', prefix: 'Protected', url: 'https://api.example.com', kind: RemoteSourceKind.Custom, headers: { Authorization: 'Bearer api-token', 'X-API-Key': 'your-api-key', }, }); // REST remote source with OAuth client.createRESTRemoteSource({ displayName: 'OAuth REST API', prefix: 'OAuth', url: 'https://api.example.com', kind: RemoteSourceKind.Custom, oAuth: { clientId: 'your-client-id', clientSecret: 'your-client-secret', scopes: ['read:products', 'write:products'], authorizationGrantType: OAuthGrantType.client_credentials, authorizationUrl: 'https://auth.example.com/oauth/token', }, }); ``` **Enums** - `RemoteSourceKind`: `CommerceTools`, `CommerceLayer`, `Custom` - `OAuthGrantType`: `client_credentials` ### updateRESTRemoteSource() Updates an existing REST remote source. **Use cases:** - Updating REST API URLs. - Modifying OAuth configuration. - Adding/updating remote type definitions. - Changing authentication settings. **Important notes:** - `prefix` cannot be changed (immutable). - `remoteTypeDefinitionsToUpsert` uses create/update/delete structure. - OAuth configuration can be updated. - URL can be changed if API endpoint changes. ```ts client.updateRESTRemoteSource({ prefix: string, // Required: Remote source prefix (used to identify the source) displayName: string, // Required: New display name description?: string, // Optional: New description url?: string, // Optional: New REST API base URL headers?: JSON, // Optional: Custom headers (JSON object) remoteTypeDefinitionsToUpsert?: { // Optional: Manage remote type definitions remoteTypeDefinitionsToCreate?: Array<{ // Optional: Create new type definitions sdl: string // Required: GraphQL SDL string }>, remoteTypeDefinitionsToUpdate?: Array<{ // Optional: Update existing type definitions apiId: string, // Required: Type definition API ID sdl?: string // Optional: New SDL string }>, remoteTypeDefinitionsToDelete?: Array<{ // Optional: Delete type definitions apiId: string // Required: Type definition API ID }> }, debugEnabled?: boolean, // Optional: Enable/disable debug mode kind?: RemoteSourceKind, // Optional: Remote source kind enum oAuth?: { // Optional: OAuth configuration clientId: string, // Required: OAuth client ID clientSecret?: string, // Optional: OAuth client secret scopes?: string[], // Optional: OAuth scopes authorizationGrantType: OAuthGrantType, // Required: OAuth grant type authorizationUrl: string // Required: OAuth authorization URL } }) ``` ```ts // Basic update - change display name and URL client.updateRESTRemoteSource({ prefix: 'External', displayName: 'Updated REST API', url: 'https://api.example.com/new-endpoint', }); // Update headers client.updateRESTRemoteSource({ prefix: 'External', displayName: 'External REST API', headers: { Authorization: 'Bearer new-token', 'X-API-Key': 'new-api-key', }, }); // Add new remote type definitions client.updateRESTRemoteSource({ prefix: 'External', displayName: 'External REST API', remoteTypeDefinitionsToUpsert: { remoteTypeDefinitionsToCreate: [ { sdl: ` type Product { id: ID! name: String! price: Float! } `, }, ], }, }); ``` **Enums** - `RemoteSourceKind`: `CommerceTools`, `CommerceLayer`, `Custom` - `OAuthGrantType`: `client_credentials` ### deleteRemoteSource() Deletes a remote source. **Use cases:** - Removing unused remote sources. - Cleaning up deprecated API integrations. - Restructuring remote integrations. **Important notes:** - Permanent deletion. This action cannot be undone. - Cannot delete if used by remote fields. - Must delete all remote fields using the remote source first. - Deletes all remote type definitions. ```ts client.deleteRemoteSource({ prefix: string // Required: The prefix of the remote source to delete }) ``` ```ts // Delete a remote source client.deleteRemoteSource({ prefix: 'External', }); console.log('Remote source deleted'); ``` ## Remote fields ### createRemoteField() Creates a remote field that fetches data from a GraphQL or REST remote source. **Use cases:** - Fetching data from external APIs. - Displaying remote content in Hygraph. - Building hybrid content systems. **Important notes:** - Requires existing remote source (remoteSourceApiId). - `remoteConfig` defines how to fetch data. - `inputArgs` allow passing parameters to remote API. - Field type determined by remote source type (GRAPHQL or REST). ```ts client.createRemoteField({ apiId: string, // Required: Field API ID parentApiId: string, // Required: API ID of parent model type: RemoteFieldType, // Required: GRAPHQL or REST displayName: string, // Required: Display name description?: string, // Optional: Description tableRenderer?: string, // Optional: Table renderer identifier formRenderer?: string, // Optional: Form renderer identifier tableExtension?: string, // Optional: Table extension identifier formExtension?: string, // Optional: Form extension identifier formConfig?: JSON, // Optional: Form configuration JSON tableConfig?: JSON, // Optional: Table configuration JSON isList?: boolean, // Optional: Whether field accepts multiple values (default: false) // WARNING: Converting existing fields to/from list may cause data loss isRequired?: boolean, // Optional: Whether field is required (default: false) // When changing to true, provide migrationValue for existing null entries visibility?: VisibilityTypes, // Optional: Visibility setting position?: number, // Optional: Field position remoteConfig: { // Required: Configuration for fetching remote data returnTypeApiId: string, // Required: API ID of return type defined in remote source // Must exist in the remote source's type definitions remoteSourcePrefix: string, // Required: Prefix of the remote source to use // Must match prefix from createGraphQLRemoteSource or createRESTRemoteSource method: RemoteFieldApiMethod, // Required: HTTP method (GET or POST) // GET for simple queries, POST for mutations or complex queries headers?: JSON, // Optional: Custom HTTP headers (JSON object) // Example: { "Authorization": "Bearer token" } cacheTTLSeconds?: number, // Optional: Cache duration in seconds // 0 = no cache, default varies by remote source type graphQLQuery?: string, // Required for GRAPHQL type remote fields // Complete GraphQL query string restPath?: string, // Required for REST type remote fields // API endpoint path (e.g., "/users/{id}") // Can include path parameters in curly braces forwardClientHeaders?: boolean // Optional: Pass client request headers to remote API (default: false) // Useful for authentication/authorization }, inputArgs?: Array<{ // Optional: Arguments passed to remote field query remoteTypeApiId: string, // Required: API ID of input type from remote source apiId: string, // Required: Argument name (used in GraphQL query or REST path) isRequired: boolean, // Required: Whether this argument must be provided isList: boolean // Required: Whether argument accepts array of values }> }) ``` ```ts // GraphQL remote field client.createRemoteField({ parentApiId: 'Product', apiId: 'externalData', displayName: 'External Data', type: RemoteFieldType.GRAPHQL, remoteConfig: { returnTypeApiId: 'ExternalProduct', remoteSourcePrefix: 'External', method: RemoteFieldApiMethod.POST, graphQLQuery: ` query GetProduct($id: ID!) { product(id: $id) { id name price } } `, headers: { Authorization: 'Bearer token', }, cacheTTLSeconds: 300, }, }); // REST remote field client.createRemoteField({ parentApiId: 'Product', apiId: 'externalInventory', displayName: 'External Inventory', type: RemoteFieldType.REST, remoteConfig: { returnTypeApiId: 'Inventory', remoteSourcePrefix: 'External', method: RemoteFieldApiMethod.GET, restPath: '/inventory/{id}', headers: { 'X-API-Key': 'api-key', }, forwardClientHeaders: true, cacheTTLSeconds: 60, }, }); ``` **Enum** - `RemoteFieldType`: `GRAPHQL`, `REST` - `RemoteFieldApiMethod`: `GET`, `POST` - `VisibilityTypes`: `READ_WRITE`, `READ_ONLY`, `HIDDEN`, `API_ONLY` ### updateRemoteField() This example shows how to update an existing remote field. **Use cases:** - Updating remote field configuration. - Changing API endpoints or methods. - Modifying input arguments. **Important notes:** - `remoteConfig` can be updated. - `inputArgs` can be modified. - Field type cannot be changed. ```ts client.updateRemoteField({ apiId: string, // Required: Current field API ID parentApiId: string, // Required: Parent model API ID newApiId?: string, // Optional: New API ID (to rename the field) displayName?: string, // Optional: New display name description?: string, // Optional: New description isList?: boolean, // Optional: Whether field accepts multiple values (default: false) // WARNING: Converting existing fields to/from list may cause data loss isRequired?: boolean, // Optional: Whether field is required // When changing to true, provide migrationValue for existing null entries visibility?: VisibilityTypes, // Optional: Visibility setting formConfig?: JSON, // Optional: Form configuration JSON tableConfig?: JSON, // Optional: Table configuration JSON extensions?: JSON, // Optional: Extensions JSON meta?: JSON, // Optional: Meta JSON position?: number, // Optional: Field position remoteConfig?: { // Optional: Update remote field configuration returnTypeApiId?: string, // Optional: New return type API ID remoteSourcePrefix?: string, // Optional: New remote source prefix method?: RemoteFieldApiMethod, // Optional: HTTP method headers?: JSON, // Optional: Custom headers cacheTTLSeconds?: number, // Optional: Cache TTL in seconds graphQLQuery?: string, // Optional: GraphQL query (for GRAPHQL type) restPath?: string, // Optional: REST path (for REST type) forwardClientHeaders?: boolean // Optional: Forward client headers }, inputArgs?: { // Optional: Update input arguments (add/remove/update structure) fieldInputArgsToCreate?: Array<{ // Optional: Create new input arguments remoteTypeApiId: string, // Required: API ID of the remote input type apiId: string, // Required: Argument API ID isRequired: boolean, // Required: Whether argument is required // When changing to true, provide migrationValue for existing null entries isList: boolean // Required: Whether argument is a list }>, fieldInputArgsToUpdate?: Array<{ // Optional: Update existing input arguments argApiId: string, // Required: Current argument API ID remoteTypeApiId?: string, // Optional: New remote type API ID apiId?: string, // Optional: New argument API ID isRequired?: boolean, // Optional: Whether argument is required // When changing to true, provide migrationValue for existing null entries isList?: boolean // Optional: Whether argument is a list }>, fieldInputArgsToDelete?: Array<{ // Optional: Delete input arguments argApiId: string // Required: Argument API ID to delete }> } }) ``` ```ts // Basic update - rename field and change display name client.updateRemoteField({ apiId: 'externalData', parentApiId: 'Product', newApiId: 'externalProductData', displayName: 'External Product Data', }); // Update remote config - change GraphQL query client.updateRemoteField({ apiId: 'externalData', parentApiId: 'Product', remoteConfig: { graphQLQuery: ` query GetProduct($id: ID!) { product(id: $id) { id name price description } } `, cacheTTLSeconds: 600, }, }); // Add new input arguments client.updateRemoteField({ apiId: 'externalData', parentApiId: 'Product', inputArgs: { fieldInputArgsToCreate: [ { remoteTypeApiId: 'Boolean', apiId: 'includeReviews', isRequired: false, isList: false, }, ], }, }); ``` **Enum** - `RemoteFieldType`: `GRAPHQL`, `REST` - `RemoteFieldApiMethod`: `GET`, `POST` - `VisibilityTypes`: `READ_WRITE`, `READ_ONLY`, `HIDDEN`, `API_ONLY` ## Locales ### createLocale() Creates a new locale (language/region) for content localization. **Use cases:** - Setting up multi-language content - Adding new language support - Configuring localization **Important notes:** - Locale code must follow ISO 639-1 format, such as `en`, `de`, `fr`. - `isDefault` determines default locale - Only one locale can be default - Locales are environment-specific ```ts client.createLocale({ apiId: string, // Required: Locale API ID (e.g., "en", "en_US", "de") displayName: string, // Required: Display name (e.g., "English", "English (US)", "German") description?: string // Optional: Description }) ``` ```ts // Basic locale creation client.createLocale({ apiId: 'en', displayName: 'English', description: 'English language', }); // Locale with region client.createLocale({ apiId: 'en_US', displayName: 'English (US)', description: 'English - United States', }); // Multiple locales client.createLocale({ apiId: 'de', displayName: 'German', description: 'German language', }); client.createLocale({ apiId: 'fr', displayName: 'French', description: 'French language', }); ``` ### updateLocale() Updates an existing locale. **Use cases:** - Changing default locale. - Updating locale display names. - Modifying locale settings. **Important notes:** - Changing `isDefault` to `true` sets this as default and removes default from others. - Locale `apiId` cannot be changed. ```ts client.updateLocale({ apiId: string, // Required: Current locale API ID newApiId?: string, // Optional: New API ID (to rename the locale) displayName?: string, // Optional: New display name description?: string, // Optional: New description isDefault?: boolean // Optional: Whether this locale is the default }) ``` ```ts // Basic update - rename locale and change display name client.updateLocale({ apiId: 'en', newApiId: 'en_US', displayName: 'English (US)', description: 'English - United States', }); // Set locale as default client.updateLocale({ apiId: 'en', isDefault: true, }); // Update only display name client.updateLocale({ apiId: 'de', displayName: 'Deutsch', }); ``` ### deleteLocale() Deletes a locale. **Use cases:** - Removing language support. - Cleaning up unused locales. **Important notes:** - Permanent deletion. This action cannot be undone. - Cannot delete default locale. - May affect localized content. ```ts client.deleteLocale({ apiId: string, // Required: The API ID of the locale to delete force?: boolean // Optional: Force delete even if locale has content }) ``` ```ts // Delete a locale client.deleteLocale({ apiId: 'de', }); // Force delete a locale (even if it has content) client.deleteLocale({ apiId: 'fr', force: true, }); console.log('Locale deleted'); ``` ## Stages ### createStage() Creates a new content stage, such as `Draft`, `Published`, `Archived`. **Use cases:** - Setting up content publishing stages (`Draft`, `Published`, `Archived`). - Creating content lifecycle stages. - Building stage-based content workflows. **Important notes:** - Stage `apiId` typically `UPPERCASE`, such as, `DRAFT`, `PUBLISHED`. - `color` uses `ColorPalette` enum. - Stages are environment-specific. - `position` determines display order. ```ts client.createStage({ apiId: string, // Required: Stage API ID (e.g., "DRAFT", "PUBLISHED", "ARCHIVED") displayName: string, // Required: Display name (e.g., "Draft", "Published", "Archived") color: ColorPalette, // Required: Stage color enum description?: string, // Optional: Description position?: number // Optional: Stage position (for ordering) }) ``` ```ts // Basic stage creation client.createStage({ apiId: 'DRAFT', displayName: 'Draft', color: ColorPalette.BLUE, description: 'Work in progress', }); // Published stage client.createStage({ apiId: 'PUBLISHED', displayName: 'Published', color: ColorPalette.GREEN, description: 'Published content', }); // Stage with position client.createStage({ apiId: 'REVIEW', displayName: 'In Review', color: ColorPalette.YELLOW, description: 'Awaiting review', position: 1, }); ``` **Enum** - `ColorPalette`: `PURPLE`, `TEAL`, `YELLOW`, `GREEN`, `BLUE`, `RED`, `PINK`, `BROWN`, `ORANGE`, `INDIGO`, `OLIVE`, `ROSE`, `NEUTRAL` ### updateStage() Updates an existing content stage. **Use cases:** - Renaming stages. - Changing stage colors. - Updating stage descriptions. - Reordering stages. **Important notes:** - Uses `display` (not `displayName`) for display name. - Can change color for visual distinction. - `position` controls ordering. ```ts client.updateStage({ apiId: string, // Required: Current stage API ID newApiId?: string, // Optional: New API ID (to rename the stage) display?: string, // Optional: New display name description?: string, // Optional: New description color?: ColorPalette, // Optional: New stage color enum position?: number // Optional: New stage position }) ``` ```ts // Basic update - rename stage and change display name client.updateStage({ apiId: 'DRAFT', newApiId: 'DRAFT_STAGE', display: 'Draft Stage', description: 'Work in progress', }); // Update color client.updateStage({ apiId: 'DRAFT', color: ColorPalette.BLUE, }); // Update display name client.updateStage({ apiId: 'PUBLISHED', display: 'Published Content', }); // Update position client.updateStage({ apiId: 'REVIEW', position: 1, }); ``` **Enum** - `ColorPalette`: `PURPLE`, `TEAL`, `YELLOW`, `GREEN`, `BLUE`, `RED`, `PINK`, `BROWN`, `ORANGE`, `INDIGO`, `OLIVE`, `ROSE`, `NEUTRAL` ### deleteStage() Deletes a content stage. **Use cases:** - Removing unused stages. - Cleaning up test stages. **Important notes:** - Permanent deletion. This action cannot be undone. - Cannot delete if used by entries. - May affect published content. ```ts client.deleteStage({ apiId: string, // Required: The API ID of the stage to delete }) ``` ```ts // Delete a stage client.deleteStage({ apiId: 'ARCHIVED', }); console.log('Stage deleted'); ``` ## Taxonomies Taxonomies organize content into hierarchical categories. ### createTaxonomy() Creates a taxonomy with hierarchical nodes. **Use cases:** - Creating hierarchical category systems. - Building tag hierarchies. - Setting up nested classification systems. **Important notes:** - Can create with either `rootNode` OR `taxonomyNodes`. - `rootNode` approach: provides root node, children become taxonomy nodes. - `taxonomyNodes` approach: provides all nodes, root has parent: null. - Taxonomy `apiId` must be PascalCase and unique. ```ts // Approach 1: Hierarchical structure (easier to read) client.createTaxonomy({ apiId: string, // Required: Taxonomy API ID displayName: string, // Required: Display name description?: string, // Optional: Description rootNode?: { // Optional: Root node with children (mutually exclusive with taxonomyNodes) apiId: string, // Required: Root node API ID displayName: string, // Required: Root node display name children?: Array<{ // Optional: Child nodes (recursive structure) apiId: string, // Required: Child node API ID displayName: string, // Required: Child node display name children?: Array<...> // Optional: Nested children (recursive) }> }, }) // Approach 2: Flat array (easier for programmatic generation) client.createTaxonomy({ apiId: string, // Required: Taxonomy API ID displayName: string, // Required: Display name description?: string, // Optional: Description taxonomyNodes?: Array<{ // Optional: Flat array of nodes (mutually exclusive with rootNode) apiId: string, // Required: Node API ID displayName: string, // Required: Node display name parent?: string // Optional: Parent node API ID (null for root node) }> }) ``` ```ts // Basic taxonomy with rootNode (nested structure) client.createTaxonomy({ apiId: 'BlogCategories', displayName: 'Blog Categories', description: 'Hierarchical blog categorization', rootNode: { apiId: 'technology', displayName: 'Technology', children: [ { apiId: 'webDev', displayName: 'Web Development', children: [ { apiId: 'react', displayName: 'React' }, { apiId: 'vue', displayName: 'Vue.js' }, ], }, ], }, }); // Taxonomy with flat taxonomyNodes array client.createTaxonomy({ apiId: 'BlogCategories', displayName: 'Blog Categories', taxonomyNodes: [ { apiId: 'technology', displayName: 'Technology', parent: null }, { apiId: 'webDev', displayName: 'Web Development', parent: 'technology' }, ], }); ``` ### updateTaxonomy() Updates metadata of an existing taxonomy. Nodes are managed separately. **Use cases:** - Renaming taxonomies. - Updating taxonomy descriptions. - Changing taxonomy display names. **Important notes:** - Can change `displayName` and `description`. - Cannot change `apiId` directly (use `newApiId`). - Renaming updates all references to the taxonomy. ```ts // Update a taxonomy client.updateTaxonomy({ apiId: string, // Required: Current taxonomy API ID newApiId?: string, // Optional: New API ID (to rename the taxonomy) displayName?: string, // Optional: New display name description?: string // Optional: New description }) ``` ```ts // Basic update - rename taxonomy and change display name client.updateTaxonomy({ apiId: 'BlogCategories', newApiId: 'ArticleCategories', displayName: 'Article Categories', description: 'Updated categorization system', }); // Update only display name client.updateTaxonomy({ apiId: 'BlogCategories', displayName: 'Content Categories', }); ``` ### deleteTaxonomy() Deletes a taxonomy and all its nodes. You cannot delete a taxonomy if any fields reference it. **Use cases:** - Removing unused taxonomies. - Cleaning up deprecated category systems. - Restructuring classification systems. **Important notes:** - Permanent deletion. This action cannot be undone. - Deletes all taxonomy nodes. - Cannot delete if used by taxonomy fields. - Must delete all taxonomy fields using the taxonomy first. ```ts client.deleteTaxonomy({ apiId: string // Required: The API ID of the taxonomy to delete }) ``` ```ts // Delete a taxonomy client.deleteTaxonomy({ apiId: 'BlogCategories', }); console.log('Taxonomy deleted'); ``` ### createTaxonomyNode() Adds a new node to an existing taxonomy. **Use cases:** - Adding categories to existing taxonomies. - Building nested category structures. - Expanding taxonomy hierarchies. **Important notes:** - `parentApiId: null` creates a root-level node. - `parentApiId` references another taxonomy node's apiId. - Nodes can be nested to any depth. ```ts client.createTaxonomyNode({ taxonomyApiId: string, // Required: API ID of the taxonomy apiId: string, // Required: Node API ID displayName: string, // Required: Node display name parent?: string, // Optional: Parent node API ID (null or omit for root node) children?: Array<{ // Optional: Child nodes (nested structure) apiId: string, // Required: Child node API ID displayName: string, // Required: Child node display name children?: Array<...> // Optional: Nested children (recursive) }> }) ``` ```ts // Create a root node (no parent) client.createTaxonomyNode({ taxonomyApiId: 'BlogCategories', apiId: 'technology', displayName: 'Technology', }); // Create a child node client.createTaxonomyNode({ taxonomyApiId: 'BlogCategories', apiId: 'webDev', displayName: 'Web Development', parent: 'technology', // Parent node API ID }); // Create a node with nested children client.createTaxonomyNode({ taxonomyApiId: 'BlogCategories', apiId: 'technology', displayName: 'Technology', parent: null, // Root node children: [ { apiId: 'webDev', displayName: 'Web Development', children: [ { apiId: 'react', displayName: 'React' }, { apiId: 'vue', displayName: 'Vue.js' }, ], }, ], }); ``` ### updateTaxonomyNode() Updates an existing taxonomy node. **Use cases:** - Moving taxonomy nodes (changing parent). - Renaming taxonomy nodes. - Updating node descriptions. - Reorganizing taxonomy hierarchy. **Important notes:** - `parent` parameter changes the node's parent (can move nodes). - Set `parent: null` to make a node root-level. - Can change `apiId` using `newApiId`. - Moving nodes updates the entire hierarchy. ```ts client.updateTaxonomyNode({ taxonomyApiId: string, // Required: API ID of the taxonomy apiId: string, // Required: Current node API ID newApiId?: string, // Optional: New API ID (to rename the node) displayName?: string, // Optional: New display name parent?: string | null // Optional: New parent node API ID (null for root node) }) ``` ```ts // Basic update - rename node and change display name client.updateTaxonomyNode({ taxonomyApiId: 'BlogCategories', apiId: 'webDev', newApiId: 'webDevelopment', displayName: 'Web Development', }); // Move node to different parent client.updateTaxonomyNode({ taxonomyApiId: 'BlogCategories', apiId: 'react', parent: 'frontend', // Move 'react' under 'frontend' node }); // Move node to root (make it a root node) client.updateTaxonomyNode({ taxonomyApiId: 'BlogCategories', apiId: 'technology', parent: null, // Make it a root node }); ``` ### deleteTaxonomyNode() Deletes a taxonomy node and all its children. **Use cases:** - Removing unused taxonomy nodes. - Cleaning up deprecated categories. - Simplifying taxonomy hierarchies. **Important notes:** - Permanent deletion. This action cannot be undone. - Deletes the node and all its children (cascading deletion). - Cannot delete if used by taxonomy fields. - Must update or delete taxonomy fields using the node first. ```ts client.deleteTaxonomyNode({ taxonomyApiId: string, // Required: API ID of the taxonomy apiId: string // Required: API ID of the node to delete }) ``` ```ts // Delete a taxonomy node client.deleteTaxonomyNode({ taxonomyApiId: 'BlogCategories', apiId: 'webDev', }); console.log('Taxonomy node deleted'); ``` ### createTaxonomyField() Creates a taxonomy field that links a model to a taxonomy. **Use cases:** - Linking models to taxonomies. - Adding category/tag fields to content. - Creating classification systems. **Important notes:** - `initialValue` and `migrationValue` must be JSON stringified. - Single value: `JSON.stringify('nodeApiId')`. - List value: `JSON.stringify(['node1', 'node2'])`. - Cannot use `isUnique` with `initialValue` or `migrationValue`. - `migrationValue` is used for existing data, `initialValue` for new entries. ```ts client.createTaxonomyField({ apiId: string, // Required: Field API ID parentApiId: string, // Required: API ID of parent model taxonomyApiId: string, // Required: API ID of the taxonomy to link to displayName: string, // Required: Display name description?: string, // Optional: Description isVariantEnabled?: boolean, // Optional: Enable variant support isList?: boolean, // Optional: Whether field accepts multiple values (default: false) // WARNING: Converting existing fields to/from list may cause data loss isRequired?: boolean, // Optional: Whether field is required (default: false) // When changing to true, provide migrationValue for existing null entries isUnique?: boolean, // Optional: Whether field is unique (default: false) // CANNOT be used with initialValue or migrationValue initialValue?: string, // Optional: JSON stringified initial value (for new entries) // If isList is true, this should be a JSON stringified array of apiIds // If isList is false, this should be a JSON stringified apiId migrationValue?: string, // Optional: JSON stringified taxonomy node API ID for initial value visibility?: VisibilityTypes, // Optional: Visibility setting position?: number, // Optional: Field position visibilityCondition?: { // Optional: Show/hide field based on another field's value baseField: string, // Required: API ID of the field that controls visibility operator: FieldConditionOperator, // Required: Comparison operator (IS, IS_NOT, etc.) enumerationValues?: string[], // Required when baseField is enumeration type booleanValue?: boolean // Required when baseField is boolean type }, isSystem?: boolean // Optional: System field flag (only for AppTokens) }) ``` ```ts // Basic taxonomy field - single selection client.createTaxonomyField({ parentApiId: 'Post', apiId: 'category', displayName: 'Category', taxonomyApiId: 'BlogCategories', isRequired: false, }); // Taxonomy field with list support (multiple selections) client.createTaxonomyField({ parentApiId: 'Post', apiId: 'categories', displayName: 'Categories', taxonomyApiId: 'BlogCategories', isList: true, isRequired: false, }); // Taxonomy field with initial value client.createTaxonomyField({ parentApiId: 'Post', apiId: 'category', displayName: 'Category', taxonomyApiId: 'BlogCategories', initialValue: JSON.stringify('technology'), // JSON stringified taxonomy node API ID }); ``` **Enums** - `VisibilityTypes`: `READ_WRITE`, `READ_ONLY`, `HIDDEN`, `API_ONLY` - `FieldConditionOperator`: `IS`, `IS_NOT`, `CONTAINS_ANY`, `CONTAINS_ALL`, `CONTAINS_NONE` ### updateTaxonomyField() Updates an existing taxonomy field. **Use cases:** - Renaming taxonomy fields. - Updating field visibility. - Changing required/unique status. - Updating initial/migration values. - Adding conditional visibility. - Changing taxonomy reference. **Important notes:** - `initialValue` and `migrationValue` must be JSON stringified. - Single value: `JSON.stringify('nodeApiId')`. - List value: `JSON.stringify(['node1', 'node2'])`. - Cannot use `isUnique` with `initialValue` or `migrationValue`. - When changing `isRequired` to `true`, provide `migrationValue` for existing null entries. - Cannot change `isList` without potential data loss. - `visibilityCondition` can be set to `null` to remove it. - Cannot change `taxonomyApiId`. Taxonomy reference is immutable. ```ts client.updateTaxonomyField({ apiId: string, // Required: Current field API ID newApiId?: string, // Optional: New API ID (to rename the field) parentApiId?: string, // Optional: Parent model API ID displayName?: string, // Optional: New display name description?: string, // Optional: New description isVariantEnabled?: boolean, // Optional: Enable/disable variant support isRequired?: boolean, // Optional: Whether field is required // When changing to true, provide migrationValue for existing null entries isUnique?: boolean, // Optional: Whether field is unique // CANNOT be used with initialValue or migrationValue visibility?: VisibilityTypes, // Optional: Visibility setting position?: number, // Optional: Field position initialValue?: string, // Optional: JSON stringified initial value (for new entries) migrationValue?: string, // Optional: JSON stringified taxonomy node API ID (for existing data) visibilityCondition?: { // Optional: Show/hide field based on another field's value baseField: string, // Required: API ID of the field that controls visibility operator: FieldConditionOperator, // Required: Comparison operator (IS, IS_NOT, etc.) enumerationValues?: string[], // Required when baseField is enumeration type booleanValue?: boolean // Required when baseField is boolean type }, isSystem?: boolean // Optional: System field flag (only for AppTokens) }) ``` ```ts // Basic update - rename field and change display name client.updateTaxonomyField({ apiId: 'category', parentApiId: 'Post', newApiId: 'primaryCategory', displayName: 'Primary Category', }); // Update initial value client.updateTaxonomyField({ apiId: 'category', parentApiId: 'Post', initialValue: JSON.stringify('technology'), // Single value }); // Update initial value for list field client.updateTaxonomyField({ apiId: 'categories', parentApiId: 'Post', initialValue: JSON.stringify(['technology', 'business']), // Array }); ``` **Enums** - `VisibilityTypes`: `READ_WRITE`, `READ_ONLY`, `HIDDEN`, `API_ONLY` - `FieldConditionOperator`: `IS`, `IS_NOT`, `CONTAINS_ANY`, `CONTAINS_ALL`, `CONTAINS_NONE` ## Workflows ### createWorkflow() **Use cases:** - Setting up content approval processes. - Creating editorial workflows. - Building multi-step content review systems. **Important notes:** - Steps are created in the order provided unless position is specified. If a position conflicts, the new step is inserted at the next available position. - First step cannot have `returnToStep`. - `roleOverrides` at workflow level bypasses all steps. - `allowedRoles` at step level controls who can work at that step. - `publishStages` determines which stages can be published from a step. - Steps with conflicting positions are inserted at next available slot. ```ts client.createWorkflow({ apiId: string, // Required: Workflow API ID displayName: string, // Required: Display name description?: string, // Optional: Description enabled: boolean, // Required: Whether workflow is enabled modelApiIds?: string[], // Optional: Array of model API IDs to apply workflow to roleOverrides?: string[], // Optional: Roles that can bypass workflow entirely // Different from step-level allowedRoles // Users with these roles can skip all workflow steps steps: Array<{ // Optional: Workflow steps (created in order provided) apiId: string, // Required: Step API ID (unique within workflow) displayName: string, // Required: Step display name shown in UI description?: string, // Optional: Step description color: ColorPalette, // Required: Step color (visual indicator in UI) allowEdit: boolean, // Required: Whether entries can be edited at this step returnToStep?: string, // Optional: Previous step to return to // CANNOT be set for first step! // Must reference existing step API ID position?: number, // Optional: Step order (0-based index) // If omitted, steps created in array order // If position conflicts, inserted at next available slot allowedRoles: string[], // Required: Roles that can work at THIS specific step // Different from workflow-level roleOverrides publishStages?: string[] // Optional: Stages that can be published from this step // Empty array or omitted = no publishing from this step }> }) ``` ```ts // Basic workflow with steps client.createWorkflow({ apiId: 'editorialWorkflow', displayName: 'Editorial Workflow', description: 'Content review and approval process', enabled: true, modelApiIds: ['Post', 'Page'], steps: [ { apiId: 'draft', displayName: 'Draft', description: 'Work in progress', color: ColorPalette.BLUE, allowEdit: true, allowedRoles: ['editor', 'author'], }, { apiId: 'review', displayName: 'In Review', description: 'Awaiting editorial review', color: ColorPalette.YELLOW, allowEdit: false, allowedRoles: ['editor'], returnToStep: 'draft', }, { apiId: 'approved', displayName: 'Approved', description: 'Ready for publication', color: ColorPalette.GREEN, allowEdit: false, allowedRoles: ['editor', 'admin'], publishStages: ['PUBLISHED'], }, ], }); ``` **Enum** - `ColorPalette`: `PURPLE`, `TEAL`, `YELLOW`, `GREEN`, `BLUE`, `RED`, `PINK`, `BROWN`, `ORANGE`, `INDIGO`, `OLIVE`, `ROSE`, `NEUTRAL` ### updateWorkflow() Updates an existing workflow. **Use cases:** - Adding/removing steps. - Updating step configurations. - Changing workflow models or roles. - Modifying step order. **Important notes:** - `modelApiIds` uses add/remove structure. - `roleOverrides` uses add/remove structure. - `steps` has create/update/delete structure. - Step `allowedRoles` uses add/remove structure - Step `publishStages` uses add/remove structure. ```ts client.updateWorkflow({ apiId: string, // Required: Current workflow API ID newApiId?: string, // Optional: New API ID (to rename the workflow) displayName?: string, // Optional: New display name description?: string, // Optional: New description enabled?: boolean, // Optional: Whether workflow is enabled modelApiIds?: { // Optional: Update models (add/remove structure) modelsToAdd?: string[], // Optional: Model API IDs to add modelsToRemove?: string[] // Optional: Model API IDs to remove }, roleOverrides?: { // Optional: Update role overrides (add/remove structure) rolesToAdd?: string[], // Optional: Role IDs to add rolesToRemove?: string[] // Optional: Role IDs to remove }, steps?: { // Optional: Update workflow steps stepsToCreate?: Array<{ // Optional: New steps to add apiId: string, // Required: Step API ID displayName: string, // Required: Step display name description?: string, // Optional: Step description color: ColorPalette, // Required: Step color allowEdit: boolean, // Required: Whether entries can be edited returnToStep?: string, // Optional: Step API ID to return to position?: number, // Optional: Step position allowedRoles: string[], // Required: Array of role IDs publishStages?: string[] // Optional: Array of stage API IDs }>, stepsToUpdate?: Array<{ // Optional: Existing steps to update apiId: string, // Required: Current step API ID displayName?: string, // Optional: New display name description?: string, // Optional: New description color?: ColorPalette, // Optional: New color allowEdit?: boolean, // Optional: Whether entries can be edited returnToStep?: string, // Optional: Step API ID to return to position?: number, // Optional: New position allowedRoles?: { // Optional: Update allowed roles (add/remove structure) rolesToAdd?: string[], // Optional: Role IDs to add rolesToRemove?: string[] // Optional: Role IDs to remove }, publishStages?: { // Optional: Update publish stages (add/remove structure) stagesToAdd?: string[], // Optional: Stage API IDs to add stagesToRemove?: string[] // Optional: Stage API IDs to remove } }>, stepsToDelete?: string[] // Optional: Array of step API IDs to delete } }) ``` ```ts // Basic update - rename workflow and change display name client.updateWorkflow({ apiId: 'editorialWorkflow', newApiId: 'contentWorkflow', displayName: 'Content Workflow', description: 'Updated workflow description', }); // Update models (add/remove) client.updateWorkflow({ apiId: 'editorialWorkflow', modelApiIds: { modelsToAdd: ['Page', 'Article'], modelsToRemove: ['Post'], }, }); // Add new step client.updateWorkflow({ apiId: 'editorialWorkflow', steps: { stepsToCreate: [ { apiId: 'archived', displayName: 'Archived', color: ColorPalette.NEUTRAL, allowEdit: false, allowedRoles: ['admin'], }, ], }, }); // Update existing step client.updateWorkflow({ apiId: 'editorialWorkflow', steps: { stepsToUpdate: [ { apiId: 'review', displayName: 'In Review', color: ColorPalette.ORANGE, allowEdit: false, }, ], }, }); ``` **Enum** - `ColorPalette`: `PURPLE`, `TEAL`, `YELLOW`, `GREEN`, `BLUE`, `RED`, `PINK`, `BROWN`, `ORANGE`, `INDIGO`, `OLIVE`, `ROSE`, `NEUTRAL` ### deleteWorkflow() Deletes a workflow and all its steps. Models using this workflow revert to the default workflow. ** Use cases:** - Removing unused workflows. - Cleaning up test workflows. - Restructuring workflow systems. **Important notes:** - Permanent deletion. This action cannot be undone. - Deletes all workflow steps. - May affect entries currently in workflow. - Consider disabling workflow first before deletion. ```ts client.deleteWorkflow({ apiId: string // Required: The API ID of the workflow to delete }) ``` ```ts // Delete a workflow client.deleteWorkflow({ apiId: 'editorialWorkflow', }); console.log('Workflow deleted'); ``` ## Webhooks ### createWebhook() Creates a webhook. **Use cases:** - Notifying external systems of content changes. - Integrating with third-party services. - Building event-driven architectures. - Syncing content to external databases. **Important notes:** - `models` and `stages` are arrays of UUIDs (not API IDs). - Empty arrays (`[]`) mean "all models/stages" including future ones. - `triggerActions` determines which operations trigger the webhook. - `triggerSources` filters by source (PAT, MEMBER, PUBLIC). - `webhookId` is a UUID, not an API ID. ```ts client.createWebhook({ name: string, // Required: Webhook name url: string, // Required: Webhook URL isActive: boolean, // Required: Whether webhook is active includePayload: boolean, // Required: Whether to include payload in request models: string[], // Required: Array of model IDs (UUIDs) - empty array for all models stages: string[], // Required: Array of stage IDs (UUIDs) - empty array for all stages triggerType: WebhookTriggerType, // Required: Trigger type enum triggerActions: WebhookTriggerAction[], // Required: Array of trigger action enums description?: string, // Optional: Description method?: WebhookMethod, // Optional: HTTP method (default: POST) headers?: JSON, // Optional: Custom headers (JSON object) secretKey?: string, // Optional: Secret key for webhook signature triggerSources?: WebhookTriggerSource[], // Optional: Array of trigger source enums isSystem?: boolean // Optional: System webhook flag (only for AppTokens) }) ``` ```ts // Basic webhook - notify on publish client.createWebhook({ name: 'Post Publish Notification', description: 'Notify marketing system when blog posts are published', url: 'https://api.marketing.example.com/webhooks/blog-published', method: WebhookMethod.POST, headers: { Authorization: 'Bearer webhook-secret-token', 'Content-Type': 'application/json', }, isActive: true, includePayload: true, models: ['post-model-uuid'], // Model ID (UUID), not API ID stages: ['published-stage-uuid'], // Stage ID (UUID), not API ID triggerType: WebhookTriggerType.CONTENT_MODEL, triggerActions: [WebhookTriggerAction.PUBLISH], }); // Webhook for all models and stages client.createWebhook({ name: 'All Content Changes', url: 'https://api.example.com/webhooks/all-changes', method: WebhookMethod.POST, isActive: true, includePayload: true, models: [], // Empty array = all models (including future ones) stages: [], // Empty array = all stages (including future ones) triggerType: WebhookTriggerType.CONTENT_MODEL, triggerActions: [ WebhookTriggerAction.CREATE, WebhookTriggerAction.UPDATE, WebhookTriggerAction.DELETE, ], }); ``` **Enums** - `WebhookMethod`: `GET`, `POST`, `PUT`, `DELETE` - `WebhookTriggerType`: `CONTENT_MODEL` - `WebhookTriggerAction`: `CREATE`, `UPDATE`, `DELETE`, `PUBLISH`, `UNPUBLISH`, `TRANSITION_STEP` - `WebhookTriggerSource`: `PAT`, `MEMBER`, `PUBLIC` ### updateWebhook() Updates an existing webhook. **Use cases:** - Updating webhook URLs. - Changing trigger conditions. - Modifying models/stages webhook applies to. **Important notes:** - `models`, `stages`, `triggerActions`, `triggerSources` replace entire lists. - `webhookId` is a UUID (not API ID). - To add or remove items, provide a complete new list. ```ts client.updateWebhook({ webhookId: string, // Required: Webhook ID (UUID) name?: string, // Optional: New webhook name description?: string, // Optional: New description url?: string, // Optional: New webhook URL method?: WebhookMethod, // Optional: HTTP method headers?: JSON, // Optional: Custom headers (JSON object) isActive?: boolean, // Optional: Whether webhook is active includePayload?: boolean, // Optional: Whether to include payload models?: string[], // Optional: Array of model IDs (UUIDs) - replaces entire list stages?: string[], // Optional: Array of stage IDs (UUIDs) - replaces entire list triggerType?: WebhookTriggerType, // Optional: Trigger type enum triggerActions?: WebhookTriggerAction[], // Optional: Array of trigger action enums (replaces entire list) secretKey?: string, // Optional: Secret key for webhook signature triggerSources?: WebhookTriggerSource[], // Optional: Array of trigger source enums (replaces entire list) isSystem?: boolean // Optional: System webhook flag (only for AppTokens) }) ``` ```ts // Basic update - change name and URL client.updateWebhook({ webhookId: 'webhook-uuid-here', name: 'Updated Webhook Name', url: 'https://api.example.com/webhooks/updated', }); // Update models and stages (replaces entire list) client.updateWebhook({ webhookId: 'webhook-uuid-here', models: ['post-model-uuid', 'page-model-uuid'], // Replaces all models stages: ['published-stage-uuid'], // Replaces all stages }); // Update trigger actions (replaces entire list) client.updateWebhook({ webhookId: 'webhook-uuid-here', triggerActions: [ WebhookTriggerAction.CREATE, WebhookTriggerAction.UPDATE, WebhookTriggerAction.DELETE, ], }); ``` **Enums** - `WebhookMethod`: `GET`, `POST`, `PUT`, `DELETE` - `WebhookTriggerType`: `CONTENT_MODEL` - `WebhookTriggerAction`: `CREATE`, `UPDATE`, `DELETE`, `PUBLISH`, `UNPUBLISH`, `TRANSITION_STEP` - `WebhookTriggerSource`: `PAT`, `MEMBER`, `PUBLIC` ### deleteWebhook() Deletes a webhook. **Use cases:** - Removing unused webhooks. - Cleaning up test webhooks. **Important notes:** - Uses `webhookId` (UUID), not API ID. - Permanent deletion. This action cannot be undone. - Cannot delete if used by enumerable fields. - Must delete all enumerable fields using the enumeration first. ```ts client.deleteWebhook({ webhookId: string // Required: The webhook ID (UUID) }) ``` ```ts // Delete a webhook client.deleteWebhook({ webhookId: 'webhook-uuid-here', }); console.log('Webhook deleted'); ``` ## Sidebar elements ### createCustomSidebarElement() Creates app-based custom sidebar element. **Use cases:** - Adding app integrations to models. - Creating custom UI elements. - Integrating third-party tools. **Important notes:** - Requires `appApiId` and `appElementApiId`. - `modelApiId` specifies which model to add element to - `position` determines order (not available in standalone method, use updateModel instead). - `config` allows passing JSON metadata. ```ts client.createCustomSidebarElement({ modelApiId: string, // Required: API ID of the model displayName: string, // Required: Display name for the sidebar element appElementApiId: string, // Required: API ID of the App element appApiId: string, // Required: API ID of the App description?: string, // Optional: Description for the sidebar element config?: JSON // Optional: JSON metadata associated with the sidebar element }) ``` ```ts // Basic custom sidebar element creation client.createCustomSidebarElement({ modelApiId: 'Product', displayName: 'Product Analytics', description: 'View product analytics', appElementApiId: 'analytics-dashboard', appApiId: 'analytics-app', config: { refreshInterval: 30, showChart: true, }, }); // Custom sidebar element with minimal config client.createCustomSidebarElement({ modelApiId: 'Post', displayName: 'SEO Preview', appElementApiId: 'seo-preview', appApiId: 'seo-app', }); ``` ### deleteCustomSidebarElement() Deletes custom sidebar element. **Use cases:** - Removing app integrations. - Cleaning up unused sidebar elements. **Important notes:** - Requires `appApiId`, `appElementApiId`, and `modelApiId`. - Permanent deletion. This action cannot be undone. ```ts client.deleteCustomSidebarElement({ modelApiId: string, // Required: API ID of the model appApiId: string, // Required: API ID of the App appElementApiId: string // Required: API ID of the App element }) ``` ```ts // Delete a custom sidebar element client.deleteCustomSidebarElement({ modelApiId: 'Product', appApiId: 'analytics-app', appElementApiId: 'analytics-dashboard', }); console.log('Custom sidebar element deleted'); ``` ### Manage sidebar elements with updateModel() Use the `updateModel` method on the client to manage sidebar elements. **Use cases:** - Bulk management of sidebar elements - Setting positions for sidebar elements - Managing both custom and system sidebar elements **Important notes:** - More powerful than standalone methods. - Allows setting position for elements. - Can create, update, and delete in single operation. - System sidebar elements: `INFORMATION`, `STAGES`, `LOCALIZATIONS`, `VERSIONS`, `PREVIEW_URLS`, `RELEASES`, `CONTENT_WORKFLOWS`, `VARIANTS`. ```ts client.updateModel({ apiId: string, // Required: Current model API ID sidebarElementsToUpsert?: { // Optional: Sidebar elements to create/update/delete customSidebarElementsToCreate?: Array<{ displayName: string, // Required: Display name description?: string, // Optional: Description config?: JSON, // Optional: JSON metadata appElementApiId: string, // Required: API ID of the App element appApiId: string, // Required: API ID of the App position?: number // Optional: Position }>, systemSidebarElementsToCreate?: Array<{ type: SystemSidebarElementType, // Required: System sidebar element type config?: JSON, // Optional: JSON metadata position?: number // Optional: Position }>, sidebarElementsToUpdate?: Array<{ displayName: string, // Required: Current display name (identifier) newDisplayName?: string, // Optional: New display name description?: string, // Optional: New description config?: JSON, // Optional: New config position?: number // Optional: New position }>, customSidebarElementsToDelete?: Array<{ appApiId: string, // Required: API ID of the App appElementApiId: string // Required: API ID of the App element }>, systemSidebarElementsToDelete?: Array<{ type: SystemSidebarElementType // Required: System sidebar element type }> } }) ``` ```ts // Add custom sidebar elements client.updateModel({ apiId: 'Product', sidebarElementsToUpsert: { customSidebarElementsToCreate: [ { displayName: 'Product Analytics', description: 'View product analytics', appElementApiId: 'analytics-dashboard', appApiId: 'analytics-app', position: 0, config: { refreshInterval: 30 }, }, ], }, }); // Add system sidebar elements client.updateModel({ apiId: 'Post', sidebarElementsToUpsert: { systemSidebarElementsToCreate: [ { type: SystemSidebarElementType.INFORMATION, position: 1, }, { type: SystemSidebarElementType.STAGES, position: 2, }, ], }, }); // Update existing sidebar elements client.updateModel({ apiId: 'Product', sidebarElementsToUpsert: { sidebarElementsToUpdate: [ { displayName: 'Product Analytics', // Current display name (identifier) newDisplayName: 'Analytics Dashboard', description: 'Updated analytics dashboard', position: 2, }, ], }, }); // Delete custom sidebar elements client.updateModel({ apiId: 'Product', sidebarElementsToUpsert: { customSidebarElementsToDelete: [ { appApiId: 'old-app', appElementApiId: 'old-element', }, ], }, }); // Delete system sidebar elements client.updateModel({ apiId: 'Post', sidebarElementsToUpsert: { systemSidebarElementsToDelete: [ { type: SystemSidebarElementType.VERSIONS, }, ], }, }); // Comprehensive sidebar management client.updateModel({ apiId: 'Product', sidebarElementsToUpsert: { customSidebarElementsToCreate: [ { displayName: 'New Analytics', appElementApiId: 'analytics-dashboard', appApiId: 'analytics-app', position: 0, }, ], systemSidebarElementsToCreate: [ { type: SystemSidebarElementType.INFORMATION, position: 1, }, ], sidebarElementsToUpdate: [ { displayName: 'Existing Element', newDisplayName: 'Updated Element', position: 2, }, ], customSidebarElementsToDelete: [ { appApiId: 'old-app', appElementApiId: 'old-element', }, ], systemSidebarElementsToDelete: [ { type: SystemSidebarElementType.VERSIONS, }, ], }, }); ``` **Enums** - `SystemSidebarElementType`: `INFORMATION`, `STAGES`, `LOCALIZATIONS`, `VERSIONS`, `PREVIEW_URLS`, `RELEASES`, `CONTENT_WORKFLOWS`, `VARIANTS` ## Conditional visibility You can modify the visibility conditions for a number of field types, such as simple, enumerable, component, relational, union, and taxonomy fields. The `visibilityCondition` parameter is available in the following methods:
- `createSimpleField()` - `createEnumerableField()` - `createComponentField()` - `createRelationalField()` - `createUnionField()` - `createTaxonomyField()`
- `updateSimpleField()` - `updateEnumerableField()` - `updateComponentField()` - `updateTaxonomyField()`
```ts visibilityCondition?: { // Optional: Show/hide field based on another field's value baseField: string, // Required: API ID of the field that controls visibility operator: FieldConditionOperator, // Required: Comparison operator (IS, IS_NOT, etc.) enumerationValues?: string[], // Required when baseField is enumeration type booleanValue?: boolean // Required when baseField is boolean type }, ``` **Enums** - `FieldConditionOperator`: `IS`, `IS_NOT`, `CONTAINS_ANY`, `CONTAINS_ALL`, `CONTAINS_NONE` ### Change visibility condition **Use cases:** - Showing/hiding fields based on other field values. - Creating dynamic forms. - Building conditional content structures. **Important notes:** - `baseField` is the API ID of the controlling field. - For enumeration fields: use `enumerationValues` array. - For boolean fields: use `booleanValue`. - Operators: `IS`, `IS_NOT`, `CONTAINS_ANY`, `CONTAINS_ALL`, `CONTAINS_NONE`. - Can be applied to: simple fields, component fields, relational fields, union fields, taxonomy fields. ```ts // For an enumerable baseField client.updateSimpleField({ apiId: "conditionalFieldOne", parentApiId: "ModelApiId", visibilityCondition: { baseField: "buildingMaterial", // apiId of the baseField (in this case an enumerable field) operator: FieldConditionOperator.IS, enumerationValues: ["plexiGlass"], // an array of apiId for the referenced enumerationValues }, }); // OR with a boolean baseField client.updateSimpleField({ apiId: "conditionalFieldTwo", parentApiId: "ModelApiId", visibilityCondition: { baseField: "isMobileApp", // apiId of the baseField (in this case a boolean field) operator: FieldConditionOperator.IS, booleanValue: true, }, }); // Change from one condition to another client.updateSimpleField({ apiId: "conditionalField", parentApiId: "ModelApiId", visibilityCondition: { baseField: "status", // Changed from "buildingMaterial" to "status" operator: FieldConditionOperator.IS, enumerationValues: ["PUBLISHED"], // Changed enumeration values }, }); ``` ### Remove visibility condition **Use cases:** - Removing conditional visibility. - Making fields always visible. - Simplifying form structure. **Important notes:** - Set `visibilityCondition: null` to remove. - Can also omit `visibilityCondition` parameter entirely. - Field becomes always visible (based on visibility setting). ```ts client.updateSimpleField({ apiId: "conditionalFieldOne", parentApiId: "ModelApiId", visibilityCondition: null, }); ``` ## Apps ### createAppInstallation() Installs an app in the environment. **Use cases:** - Installing apps in environment. - Adding app functionality. - Enabling app features. **Important notes:** - Requires `appApiId`. - `config` allows passing installation configuration. - App must exist before installation. ```ts client.createAppInstallation({ appApiId: string, // Required: API ID of the app to install config: JSON // Required: App configuration (JSON object) }) ``` ```ts // Basic app installation client.createAppInstallation({ appApiId: 'analytics-app', config: { apiKey: 'your-api-key', enabled: true, }, }); // App installation with complex config client.createAppInstallation({ appApiId: 'seo-app', config: { apiKey: 'seo-api-key', settings: { autoGenerateMetaTags: true, enableSitemap: true, sitemapUrl: 'https://example.com/sitemap.xml', }, integrations: { googleSearchConsole: { enabled: true, propertyId: 'property-id', }, }, }, }); ``` When an app is installed via the `createAppInstallation` method, it is always installed with the `PENDING` status. Users with the necessary permissions (usually the `ADMIN` or `DEVELOPER` roles) must complete the app installation process in Hygraph Studio. Currently, to use the `createAppInstallation` method, the app needs to exist in at least one environment within the project. Users can't use this method to install new apps, only apps that they've previously installed in an existing environment. ### updateAppInstallation() Updates an existing app installation. **Use cases:** - Updating app configuration. - Enabling or disabling app installation. - Modifying app settings. - Updating app integration parameters -Refreshing app configuration **Important notes:** - Only valid for App Token bearer. Must be called with an App Token (not a Permanent Auth Token). - No `appApiId` parameter. The app installation is identified by the App Token making the request. - `config` is merged with existing config (not replaced). - `status` controls app installation status. ```ts client.updateAppInstallation({ config?: JSON, // Optional: App Installation config (merged with existing config) status?: AppInstallationStatus // Optional: App Installation status }) ``` ```ts // Update app configuration client.updateAppInstallation({ config: { apiKey: 'new-api-key', refreshInterval: 60, enabled: true, }, }); // Change app status // Disable app installation client.updateAppInstallation({ status: AppInstallationStatus.DISABLED, }); // Enable app installation client.updateAppInstallation({ status: AppInstallationStatus.COMPLETED, }); // Partial config update // Existing config: { apiKey: 'old', refreshInterval: 30, theme: 'light' } // Update only refreshInterval client.updateAppInstallation({ config: { refreshInterval: 60, // Only this field is updated, others remain }, }); // Result: { apiKey: 'old', refreshInterval: 60, theme: 'light' } ``` **Enums** - `AppInstallationStatus`: `PENDING`, `COMPLETED`, `DISABLED`. ### deleteAppInstallation() Uninstalls an app from the environment. **Use cases:** - Uninstalling apps. - Removing app functionality. **Important notes:** - Permanent deletion. This action cannot be undone. - May affect sidebar elements and app fields. ```ts client.deleteAppInstallation({ appApiId: string, // Required: The API ID of the app to uninstall }) ``` ```ts // Uninstall an app client.deleteAppInstallation({ appApiId: 'analytics-app', }); console.log('App uninstalled'); ``` ### Custom renderers and app fields 1. To create a simple field with custom table and form renderers in an environment, first retrieve the `appApiId` and `appElementApiId` using the Management API. ```graphql query test { viewer { project(id: "") { environment(name: "") { appInstallation(appApiId: "") { id app { id apiId elements { id apiId type } } } } } } } ``` 2. Now, create the simple field with custom table and form renderers in the specified environment. ```ts client.createSimpleField({ environmentId: "", parentApiId: "Car", apiId: "appField", type: "STRING", displayName: "App Field", description: null, initialValue: null, tableRenderer: "CUSTOM", formRenderer: "CUSTOM", tableExtension: null, formExtension: null, formConfig: { alg_text_name: "Some custom text", appApiId: "myapp-test", appElementApiId: "myAppField" }, tableConfig: { appApiId: "myapp-test", appElementApiId: "myAppField" }, isList: false, isLocalized: false, isRequired: false, isUnique: false, isHidden: false, embeddableModels: [], visibility: "READ_WRITE", isTitle: false, position: 8, validations: null, embedsEnabled: null, visibilityCondition: null }); ``` --- # Management SDK quickstart Source: https://hygraph.com/docs/api-reference/management-sdk/management-sdk-quickstart This quickstart shows how to install the Hygraph Management SDK, authenticate against your project, and apply schema changes programmatically. You will start with a simple schema change, then learn how to group changes using a batch migration, which is the recommended approach for most production workflows. By the end of this guide, you will learn how to: - Install the Management SDK - Create a Management SDK client - Apply a schema change - Apply multiple changes using a batch migration ## Prerequisites Before you begin, make sure you have: - A Hygraph project - A [Permanent Auth Token](/docs/getting-started/access-and-permissions/api-access#permanent-auth-tokens) with [Management API permissions](/docs/getting-started/access-and-permissions/api-access#configure-management-api-permissions) - Node.js 18 or later ## Install the SDK Install the Management SDK package via npm: ```sh npm install @hygraph/management-sdk ``` ## Create a Management SDK client Create a new file, for example `management.ts`, and initialize the client with the following parameters: ```ts import { Client } from '@hygraph/management-sdk'; const client = new Client({ authToken, endpoint, name, // optional }); ``` | Option | Description | |-----------------------------|-----------------------------------------------------| | `authToken` | Permanent auth token for your project. This can be retrieved from your Hygraph project in **Project Settings > Permanent Auth Tokens > Token**. Make sure the token has proper Management API permissions depending on what you plan to execute via the SDK. | | `endpoint` | Endpoint of the High Performance Content API that belongs to the environment that you will work with. The URL can be retrieved from your Hygraph project in **Project Settings > Endpoints > High Performance Content API**. | | `name` | Optional identifier used for logging and debugging. Every migration has a unique name within an environment. If unspecified, a name will be generated and will be part of the response of a successful migration. Subsequent migrations with the same name in the same environment will fail. | For more information, [read this document](/docs/developer-guides/project/api-access). ## Add a model The Management SDK schedules changes when methods are called. Changes are applied when you call `run()`. The following example creates a simple content model: ```ts client.createModel({ apiId: 'Product', apiIdPlural: 'Products', displayName: 'Product' }) ``` If the model already exists, this call will fail. ## Add a field to the model Next, add a field to the model: ```ts client.createSimpleField({ parentApiId: 'Product', apiId: 'name', displayName: 'Name', type: SimpleFieldType.STRING, isRequired: true }) ``` ## Dry run a migration The SDK supports a `dryRun` mode. We highly recommend using this to inspect the `changes` array and validate your logic before committing changes to a production environment. A migration can be dry run to preview what changes would be applied. ```ts const changes = client.dryRun(); console.log(changes); ``` Inspect the `changes` array and validate the list of operations that will be applied to the target environment. ## Run a migration in production The `run()` method submits all scheduled operations. It collects operations from `createModel()`, `createSimpleField()`, `applySchemaChanges()`, and submits them as a batch. The `run()` method executes all operations atomically. All operations succeed or fail together. ```ts async run(foreground: boolean = true): Promise ``` | Parameter | Type | Description | |-----------------------------|-----------------|-----------------------------------------------------| | `foreground` | `boolean` | Whether to run the migration in the foreground. If `true`, the SDK client will return the results of all actions that were executed. If `false`, a successful result only means that the actions were successfully scheduled, but not executed. Default is `true`. | Returns a `MigrationInfo` object with the following properties: | Property | Description | |-----------------------------|-----------------------------------------------------| | `name` | The name of the migration. | You can call the `run()` method only once per client instance. A subsequent call returns an error: `Migration has already been executed`. ### Full example ```ts import { Client, SimpleFieldType } from '@hygraph/management-sdk'; const client = new Client({ authToken: '', endpoint: '', }); client.createModel({ apiId: 'Product', apiIdPlural: 'Products', displayName: 'Product', }); client.createSimpleField({ parentApiId: 'Product', apiId: 'name', displayName: 'Name', type: SimpleFieldType.STRING, isRequired: true, }); const result = await client.run(true); if (result.errors) { throw new Error(result.errors); } console.log(result); ``` ## Check migration status 1. Navigate to the **API Playground** in your Hygraph project. 2. In the **API selector** dropdown, select the **Management API**. 3. Run the following query to check migration status: ```graphql query MyQuery { viewer { project(id: "") { environment(name: "") { migrations { id status name errors createdAt } } } } } ``` At the moment, Hygraph only stores metadata for the last 30 migrations for each environment. Every single time a new migration is applied to an environment, metadata regarding older migrations exceeding 30, sorted by time of creation, are deleted. This means changes performed by older migrations do stay in place, however, the name and other information related to this migration will no longer be available. ## Verify changes in Studio After running the script, to verify the changes: 1. Open your Hygraph project. 2. Navigate to **Schema**. 3. Confirm that the `Product` model with the `name` field exists. If you have a development environment where you've already made and tested schema changes, you can generate a diff and apply it directly to another environment. This mode replaces the target schema. It does not merge changes. Any schema changes that exist in the target environment but not in the source will be deleted. For more information, see the [batch migration guide](/docs/api-reference/management-sdk/management-sdk-batchmigration). ## Supported operations All operations that can be executed by the SDK are listed in the TypeScript Type Definitions, Client.d.ts file. ## Migrate from the previous SDK To migrate from the previous SDK, which can be found here, there are a couple of changes that need to be made in order to use it with existing migration scripts. The old SDK has been deprecated but won't be removed from NPM, so old scripts using it will still work in the future. **New features, though, will only be available in the new SDK.** While the old SDK will continue to work, it has been deprecated and support will no longer be provided for it. First of all the import of the NPM package has to be changed. For this, the package name needs to be changed to `@hygraph/management-sdk`. In the previous version, the SDK offered a named export `newMigration` that could be used to create a new migration. This changed with the new version, so a new migration must be created as follows: ```ts import { Client } from '@hygraph/management-sdk'; const migration = new Client({ authToken: '...', endpoint: '...', }); ``` The general methods on the migration class stayed the same. So running or performing a `dryRun` will still work as before. The major change are the operations that are supported to actually execute changes: Before, the SDK was built in a fluent API style. This means you could chain operations like the following: ```ts migration .createModel({ apiId: 'Author', apiIdPlural: 'Authors', displayName: 'Author', }) .addSimpleField({ apiId: 'firstName', displayName: 'First Name', type: FieldType.String, }); ``` The new SDK version no longer supports chaining. The reason behind that is that the SDK is now fully generated by the schema it is using to execute the migration. The previous example needs to be changed as follows: ```ts migration.createModel({ apiId: 'Post', apiIdPlural: 'Posts', displayName: 'Post', }); migration.createSimpleField({ apiId: 'firstName', displayName: 'First Name', type: SimpleFieldType.STRING, modelApiId: 'Post', }); ``` To link the second operation to the first one, that is, to create the field on the created model in the first operation, you need to pass the `modelApiId` into the operation. --- # Components Source: https://hygraph.com/docs/api-reference/schema/components A component is a predefined set of fields that can be reused across models and content entries. You can think of a component as a flexible, reusable template where you define the fields that will be used inside a component once, and then fill them with different content every time you use it in a content entry. ## Querying components After a component field has been configured, it is added to the Hygraph schema and becomes immediately queryable through the API. Press `CTRL+Space` or open the Explorer view to see the available fields inside the model that contains a component field. ### Basic components Basic component fields are queried in the same way, as you would query regular fields inside your models. In the example below, a user has a `page` model with a basic `hero` component field that has `cta`, `description` and `heroImage` fields. Here's an example of a query with this setup: ```graphql query basicComponent { pages { id title mainText hero { #note that in the case of basic components, we're only using the component field's API ID, and we don't need to use the API ID of the component itself cta description heroImage { url height width } } } } ``` The query above will return a result like this: ```json { "data": { "pages": [ { "id": "cl120a7gvu57b0bt3qw4xiv86", "title": "Open positions at Hygraph", "mainText": "Follow this link to see all of the open positions - https://jobs.hygraph.com/", "hero": [ { "cta": "Explore vacancies!", "description": "There are a huge variety of positions to be filled now", "heroImage": { "url": "https://media.graphassets.com/OIi5LuhxTTOm1M2bDS9N", "height": 480, "width": 640 } }, { "cta": "Apply right now!", "description": "The application process is nice and smooth", "heroImage": { "url": "https://media.graphassets.com/bAEFpGV2S2WxiuzYcV6R", "height": 427, "width": 640 } } ] } ] } } ``` ### Modular components Modular component fields are union types “under the hood”, so querying works the same way as with relations and unions. In the example below, a user has a blogPost model with some regular fields, such as `id`, `title` and `mainText`, as well as the modular `additionalSections` component field. The modular component field has two different components: `Contributor` and `VideoBlock`. To query the two components inside the modular component field, we're going to use `... on Contributor` and `... on VideoBlock`, as we would do with a regular union type: ```graphql query modularComponent { blogPost(where: { id: "cl11z0rctt6g80bt3anxt7c11" }) { id title mainText { markdown } categories { categoryName } additionalSections { __typename # if you have multiple component instances, it's recommended to use __typename to know which fields belong to which component instance. ... on Contributor { id name jobTitle } ... on VideoBlock { id title description youTubeEmbedUrl autoplay } } } } ``` This query will return something like this: ```json { "data": { "blogPost": { "id": "cl11z0rctt6g80bt3anxt7c11", "title": "Berlin in spring", "mainText": { "markdown": "The best time to visit the German capital is the end of April\n" }, "categories": [ { "categoryName": "General" } ], "additionalSections": [ { "__typename": "VideoBlock", "id": "cl11z858dt6te0ftjxi5ucvwx", "title": "Spring in Berlin", "description": "Watch this video for some travelling inspiration", "youTubeEmbedUrl": "", "autoplay": false }, { "__typename": "Contributor", "id": "cl11z0rdbt6g90bt3eud9anlj", "name": "Daniil", "jobTitle": "Editor" } ] } } } ``` --- # Enumerations Source: https://hygraph.com/docs/api-reference/schema/enumerations ## Overview An enumeration (or enum for short) can be used to group values within a type. Enums can be useful to filter, and define certain content entries in your project. For example, a product model may contain the enum `COMMODITY`, and values `Digital` and `Physical`. Enums values can only contain alphanumeric characters, and underscores. Learn more about [Enumerations](/docs/api-reference/schema/field-types#enumerations). ## Fetching enumeration values You may want to fetch all possible values of an Enum you have created. You can do this via GraphQL using your projects API endpoint. ```graphql { __type(name: "COMMODITY") { enumValues { name } } } ``` ## Enumerations in the UI - [Create an enumeration](/docs/developer-guides/schema/using-enumerations#create-an-enumeration) - [Use the demo enumeration](/docs/developer-guides/schema/using-enumerations#use-demo-enumeration) - [Delete an enumeration](/docs/developer-guides/schema/using-enumerations#delete-an-enumeration) - [Add an enumeration to a model](/docs/developer-guides/schema/using-enumerations#add-an-enumeration-to-a-model) --- # Environment diffing Source: https://hygraph.com/docs/api-reference/schema/environment-diffing ## Overview Environment diffing is a feature in the Management API that lets you compare schemas between two environments in a project. This guide assumes the master environment is the **target** and the development environment is the **source**. You can also use the [Management SDK method](/docs/api-reference/management-sdk/management-sdk-batchmigration) to get the diff and apply schema changes. When working with multiple environments, environment diffing helps you track changes made in development compared to the master environment. This helps you identify the changes needed to align your target environment with your source environment. ## How does environment diffing work? Right after cloning, both environments have the same schema. As you apply schema changes to your development environment, it will start to diverge. To find out what exactly those differences are and then apply them to your target environment, you can create a diff. ## 1. Get environment names To create a diff, you need the names of the target and source environments. When using the **API Playground**, use the **API selector** dropdown to select the **Management API**. The query to the `ManagementApi` looks like this: ```graphql query MyQuery { viewer { project(id: "") { environments { name id } } } } ``` ## 2. Create the diff After getting the environment names, generate the diff. The response lists the changes needed to align the target with the source. To differentiate between the environments `master` and `dev`, you can use the following query to the `ManagementApi`: ```graphql { viewer { project(id: "") { environment(name: "master") { diff(environmentName: "development") { changes } } } } } ``` The response example above shows an array of objects (an ordered list of `BatchMigrations`), showing all the changes you need to apply to your target environment so that it's the same as your source environment. ## 3. Apply schema changes Use the returned list to apply updates to the target environment. Here's an example mutation in the `ManagementApi`: ```graphql mutation MyMutation($changes: [BatchMigrationChangeInput!]!) { submitBatchChanges( data: { environmentId: "", changes: $changes } ) { migration { id } } } ``` You can find your target environment ID using the first query example in this document. In the above example, `$changes` is the environment variable with value equal to the changes object in the diff. This can be supplied like so: ```json { "changes": [ { "createModel": { "apiId": "Example", "apiIdPlural": "Examples", "description": "", "displayName": "Example", "previewURLs": [] } }, ] } ``` ## Supported schema elements The following schema elements are supported: - Models - Components - Locales - Simple fields - Conditional visibility in fields - Relational fields - Enumerations - Enumerable fields - Initial values in enumeration fields - Stages - Union fields - Apps - Custom renderers and app fields - Sidebar elements - Remote fields - Remote type definitions - Remote sources **Not supported:** UI extensions. ### Apps in diffs The diff accounts for app installations, generating `createAppInstallation` and `deleteAppInstallation` operations as needed. ```json { "data": { "viewer": { "project": { "environment": { "id": "{envId}", "diff": { "changes": [ { "createAppInstallation": { "appApiId": "analytics-app", "displayName": "Analytics App", "configuration": { "apiKey": null, "trackingId": null }, "status": "INCOMPLETE_SETUP" } }, { "deleteAppInstallation": { "appApiId": "old-seo-app" } } ] } } } } } } ``` #### Custom renderers and app fields in diffs When creating or updating app fields, diffing includes configurations for custom renderers, like so: ```json { "data": { "viewer": { "project": { "environment": { "id": "{envId}", "diff": { "changes": [ { "createSimpleField": { "apiId": "appField", "parentApiId": "Car", "type": "STRING", "displayName": "App Field", "description": null, "initialValue": null, "tableRenderer": "CUSTOM", "formRenderer": "CUSTOM", "tableExtension": null, "formExtension": null, "formConfig": { "alg_text_name": "Some custom text", "appApiId": "myapp-test", "appElementApiId": "myAppField" }, "tableConfig": { "appApiId": "myapp-test", "appElementApiId": "myAppField" }, "isList": false, "isLocalized": false, "isRequired": false, "isUnique": false, "isHidden": false, "embeddableModels": [], "visibility": "READ_WRITE", "isTitle": false, "position": 8, "validations": null, "embedsEnabled": null, "visibilityCondition": null } } ] } } } } } } ``` #### Sidebar elements in diffs The diff generates create, delete, or update statements for both system and custom sidebar elements within a model. ```json { "data": { "viewer": { "project": { "environment": { "id": "{envId}", "diff": { "changes": [ { "createSidebarElement": { "parentApiId": "Car", "apiId": "customNotes", "type": "CUSTOM", "displayName": "Custom Notes", "config": { "widgetType": "CUSTOM", "helpText": "Add additional notes here" }, "position": 3 } }, { "updateSidebarElement": { "apiId": "status", "parentApiId": "Car", "newConfig": { "widgetType": "CUSTOM", "options": ["Draft", "Published", "Archived"] } } }, { "deleteSidebarElement": { "apiId": "oldWidget", "parentApiId": "Car" } } ] } } } } } } ``` ### Remote fields in diffs Environment diffing manages create, delete, and update operations for remote fields, including their source selections and specific configurations. ```json { "data": { "viewer": { "project": { "environment": { "id": "{envId}", "diff": { "changes": [ { "createRemoteField": { "apiId": "productReviews", "parentApiId": "Product", "displayName": "Product Reviews", "description": "Fetches reviews for a product from an external API", "remoteSourceId": "{remoteSourceId}", "method": "GET", "path": "/api/reviews/product/{{doc.productId}}", "returnType": "ReviewMeta", "inputArguments": [ { "apiId": "productId", "type": "STRING", "isRequired": true } ], "visibility": "READ_WRITE", "isList": false, "isLocalized": false, "isRequired": false, "isUnique": false, "isHidden": false, "position": 5 } }, { "updateRemoteField": { "apiId": "externalStock", "parentApiId": "Product", "newConfig": { "remoteFieldPath": "data.stock", "type": "INTEGER" } } }, { "deleteRemoteField": { "apiId": "legacyPrice", "parentApiId": "Product" } } ] } } } } } } ``` ### Conditional visibility in diffs Environment diffing takes into account the conditional visibility settings on all fields: ```json "visibilityCondition": { "baseField": "buildingMaterial", "operator": "IS", "booleanValue": null, "enumerationValues": [ "plexiGlass" ] } ``` ## Environment diffing limitations Please take into account the following limitations when using environment diffing. ### Schema changes in master Environment diffing does not merge changes. Instead, it replaces the schema in the target environment with the one from the source. If master changes after cloning but development does not, the diff suggests deleting unmatched changes. This can lead to content loss. A diff lists operations that make the target schema match the source. Any target changes not in the source will be replaced or overwritten. **This applies to any schema changes to your master environment**, such as adding, editing, or deleting models, fields, or sidebar widgets. To avoid issues: - Freeze schema changes in master while working in development. - Mirror changes in both environments as you go. - Manually review the diff before applying it to prevent unintended deletions. **Example situations:** - Imagine you cloned your `master` environment last week to create a `development` environment and have spent some time since then working on `development`, making changes to the schema. During that time, you also applied schema changes to your `master` environment, which you did not mirror in `development`. Later on, when using environment diffing to get the diff and apply it, the diff will find those differences, and suggest deleting the changes you made to the schema in your `master` during the last week. Remember it does not merge, but **replaces / overwrites**. - Imagine your target environment schema has a field called **Title Field**, and after cloning you change its name to **Title** in master. In this case, environment diffing would suggest to delete **Title** and create **Title Field**. Doing this would result in schemas being the same in both environments, but you will have lost the content. - Imagine you delete a field in your development environment, then create a new field with the same name, environment diffing would not detect them as different fields at all. Once you get the diff, you can apply it as is or, if necessary, edit it manually before applying to avoid content loss in the case of schema changes in the target environment. Review diff changes before applying them to ensure accuracy. **Double-check deletions to prevent content loss.** ### Required fields in diffs If you make a field required in development, you must provide a migration value. This value replaces `null` for existing content. ![Migration value in field validations](/images/docs/api-reference/schema/env-diffing-migrationvalue.png) In this case, environment diffing would suggest updating the field to `required`, but would not provide a migration value. You must include it manually in the change request before applying it to prevent it from failing. To do this, add `"migrationValue": "value"`, like so: ```json { "changes": [ { "createSimpleField": { "apiId": "newField", "parentApiId": "Post", "type": "STRING", "displayName": "NewField", "description": null, "initialValue": null, "tableRenderer": "GCMS_SINGLE_LINE", "formRenderer": "GCMS_SINGLE_LINE", "tableExtension": null, "formExtension": null, "formConfig": {}, "tableConfig": {}, "isList": false, "isLocalized": false, "isRequired": true, "isUnique": false, "isHidden": false, "embeddableModels": [], "visibility": "READ_WRITE", "isTitle": false, "position": 3, "validations": null, "embedsEnabled": null, "migrationValue": "value" } } ] } ``` This way, `"value"` will replace `null` after the diff. If you're working with a boolean field, you would need to pass the values `true` or `false` instead. ### Remote Sources with OAuth If a remote source uses `OAuth authentication`, the diff will exclude the `clientSecret`. You must manually add the actual secret before applying the changes. ```ts query EnvDiffQuery { viewer { project(id: "8845d7cc-668d-4a5b-93d9-66d947b95fe9") { environment(name: "staging") { id diff(environmentName: "master") { changes } } } } } ``` ```json { "data": { "viewer": { "project": { "environment": { "id": "010abb8e48c3460bb2649faa83cc3bee", "diff": { "changes": [ { "createRESTRemoteSource": { "debugEnabled": true, "description": "", "displayName": "CommerceLayer", "headers": {}, "kind": "CommerceLayer", "oAuth": { "authorizationGrantType": "client_credentials", "authorizationUrl": "https://hyg.commercelayer.io/oauth/token", "clientId": "F5J9l-1q8SwIRRbYuY_h1Vb-pOFzJjOTcO4HfyM_XMI", "clientSecret": "CLIENT_SECRET", "scopes": "" }, "prefix": "CommerceLayer", "url": "https://hyg.commercelayer.io/api" } } ] } } } } } } ``` You must manually replace `CLIENT_SECRET` with your actual secret before applying the batch mutation. So, for instance, this: ```json "clientSecret": "CLIENT_SECRET", ``` Would turn into this: ```json "clientSecret": "23sad-129132", //actual secret value ``` --- # Field configuration Source: https://hygraph.com/docs/api-reference/schema/field-configuration Fields define models in Hygraph. Field settings let you control how each field behaves and shape editor experience. This section describes all the configuration options available for each field in a schema, from basic attributes like display name and API ID to advanced settings for validation, localization, and visibility controls. ## Settings Each field must have the following settings configured to be added to a model: | Property | Description | | ------------ | -------------------------------------------------------------------- | | Display name | This is what is shown to content editors throughout the application | | API ID | This is what is exposed within the API as a field within your model. | | Description | Displays a hint for content editors and API users. | Not all settings are available for all [field types](/docs/api-reference/schema/field-types). ### (Rich text fields only) Embed options You can enable rich text embedding per field while adding or editing a Rich Text field. On the **Settings** tab, under **Field options**, select the **Enable embedding** checkbox, and then select the models that should be embedded in your Rich text field. This setting is permanent and cannot be edited after the initial save. ![Rich Text Options](/images/docs/api-reference/schema/rich-text-embed-options.png) ### Use as title field You can set multiple fields as titles to appear within the relational picker instead of IDs. You cannot set a field as a title this when the field visibility is set to hidden, API only or read only. | UI | Management SDK | | ------------------------------------------------------------------------------- | --------------------------------------------------------------- | | On the **Settings** tab, under **Field options**, select the **Use as title field** checkbox. | Add the `` isTitle:`true` `` parameter. [Here's an example](/docs/api-reference/management-sdk/management-sdk-methods-reference#createsimplefield). | ![Use as a title field](/images/docs/api-reference/schema/field-configuration/use-as-title.png) ### Allow multiple values You should select this if you wish to accept multiple values for the field. Setting multiple values for the field returns an array to the API. - You can select this checkbox only during field creation, and it will be read-only after that. - You cannot use the title field with this option. | UI | Management SDK | | ------------------------------------------------------------------------------- | --------------------------------------------------------------- | | On the **Settings** tab, under **Field options**, select the **Allow multiple values** checkbox. | Add the `` isList:`true` `` parameter. [Here's an example](/docs/api-reference/management-sdk/management-sdk-methods-reference#createsimplefield). | ![Allow multiple values](/images/docs/api-reference/schema/field-configuration/allow-multiple-values.png) ### Localize field Enabling this field allows translations per locale configured on your project. You cannot use **Set initial value** with this option. | UI | Management SDK | | ------------------------------------------------------------------------------- | --------------------------------------------------------------- | | On the **Settings** tab, under **Field options**, select the **Localize field** checkbox. | Add the `` isLocalized:`true` `` parameter. [Here's an example](/docs/api-reference/management-sdk/management-sdk-methods-reference#createsimplefield). | ![Localize field](/images/docs/api-reference/schema/field-configuration/localize-field.png) ### Enable variants Enabling this field allows you to add personalized versions of the main content entry. | UI | Management SDK | | ------------------------------------------------------------------------------- | --------------------------------------------------------------- | | On the **Settings** tab, under **Field options**, select the **Enable variants** checkbox. For more information, see our [Variants](/docs/developer-guides/schema/variants) docs. | Add the `` isVariantEnabled:`true` `` parameter. [Here's an example](/docs/api-reference/management-sdk/management-sdk-methods-reference#createsimplefield). | ![Enable variants](/images/docs/api-reference/schema/field-configuration/enable-variants.png) ## Validations Not all validation options are available for all [field types](/docs/api-reference/schema/field-types). ### Make field required Marking a field as required prevents a content entry from being saved if the field is left empty. The API will mark this field as non-nullable. You cannot mark a field as required when the field visibility is set to API only. | UI | Management SDK | | ------------------------------------------------------------------------------- | --------------------------------------------------------------- | | On the **Validations** tab, select the **Make field required** checkbox. | Add the `` isRequired:`true` `` parameter. [Here's an example](/docs/api-reference/management-sdk/management-sdk-methods-reference#createsimplefield). | Note the following points: - You can set hidden and read-only fields as required, but you cannot edit them in the content form. In this case, you need to provide a **Migration value** to avoid any errors when saving a content entry. - You can modify this field after initial field creation. If you want to mark a field as required **after** you've created content entries based on the model, you need to provide a **Migration value**. This migration value is automatically updated in all content entries where you previously did not provide any value. - After initial field creation, if you want to set a field as required and then unique, follow these steps: 1. Select the **Make field required** checkbox, and provide a migration value. 2. Go through every content entry for this model, and modify the value of the field so that it is unique across all entries. 3. Republish all the content entries that you changed. 4. Go back to the model, and select the **Set field as unique** checkbox. ![Make field required](/images/docs/api-reference/schema/field-configuration/make-field-required.png) ### Set field as unique Enabling this ensures content cannot be saved if the same value exists within another entry for this field. Uniqueness is checked per **Content stage**. | UI | Management SDK | | ------------------------------------------------------------------------------- | --------------------------------------------------------------- | | On the **Validations** tab, select the **Set field as unique** checkbox. | Add the `` isUnique:`true` `` parameter. [Here's an example](/docs/api-reference/management-sdk/management-sdk-methods-reference#createsimplefield). | Note the following points: - You can modify this field after initial field creation. - After field creation, if you want to set a field as unique and then required, follow these steps: 1. First, ensure that all entries have a value set for the field and republish them. 2. Then, select the **Make field required** checkbox. ![Set field as unique](/images/docs/api-reference/schema/field-configuration/set-field-unique.png) ### Limit character count This validation allows you to specify the minimum and maximum number of characters, and an optional custom error message if the requirements are not fulfilled. | UI | Management SDK | | ------------------------------------------------------------------------------- | --------------------------------------------------------------- | | On the **Validations** tab, select the **Limit character count** checkbox, and then choose one of the following options:
  • **Between** - Specify a range of allowed characters by defining the minimum and maximum length
  • **At least** - Specify the minimum number of characters required for this field.
  • **No more than** - Specify the maximum number of character allowed for this field.
| Add the `validations` object. [Here's an example](/docs/api-reference/management-sdk/management-sdk-methods-reference#createsimplefield). | ![Limit character count](/images/docs/api-reference/schema/field-configuration/limit-character-count.png) ### Match a specific pattern This validation allows you to accept a specific regular expression. To set this up, follow these steps: 1. On the **Validations** tab, select the **Match a specific pattern** checkbox. 2. From the list, choose a [pattern type](#common-patterns), and provide a regular expression. You do not need to wrap the regular expression in opening or closing slashes. 3. (Optional) Choose whether the matched pattern can be case insensitive, multiline, or single line. 4. (Optional) Provide a custom error message if the regular expression does not match the specified pattern. ![Match a specific pattern](/images/docs/api-reference/schema/field-configuration/match-field-pattern.png) #### Common patterns You can use an existing common pattern from the web app, or provide your own. **URL** ``` ^(http://www\.|https://www\.|http://|https://)?[a-z0-9]+([-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(/.*)?$ ``` **Phone** ``` ^(?:(?:\(?(?:00|\+)([1-4]\d\d|[1-9]\d?)\)?)?[\-\.\ \\\/]?)?((?:\(?\d{1,}\)?[\-\.\ \\\/]?){0,})(?:[\-\.\ \\\/]?(?:#|ext\.?|extension|x)[\-\.\ \\\/]?(\d+))?$ ``` **Email** ``` [a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])? ``` **Slug** ``` ^[a-z0-9]+(?:[-/][a-z0-9]+)*$ ``` #### Unicode characters Hygraph field validations support unicode (non-latin) characters. You can add Unicode character classes - which typically correspond to specific alphabets - to a custom Regular Expression field validation. You can read about the syntax here. ### Restrict a specific pattern This validation option allows you to _not_ accept specific regular expression. 1. On the **Validations** tab, select the **Restrict a specific pattern** checkbox. 2. From the list, choose a [pattern type](#common-patterns), and provide a regular expression. You do not need to wrap the regular expression in opening or closing slashes. 3. (Optional) Choose whether the matched pattern can be case insensitive, multiline, or single line. 4. (Optional) Provide a custom error message if the regular expression does not match the specified pattern. ![Restrict a specific pattern](/images/docs/api-reference/schema/field-configuration/restrict-field-pattern.png) ## Advanced Not all advanced settings are available for all [field types](/docs/api-reference/schema/field-types). ### Set initial value You can define an initial value for content editors. This doesn't have any effect on the API when performing mutations. | UI | Management SDK | | ------------------------------------------------------------------------------- | --------------------------------------------------------------- | | On the **Advanced** tab, select the **Set initial value** checkbox, and add the value there. | To set the initial value for a field, use the `initialValue` parameter to pass a value. [Here's an example](/docs/api-reference/management-sdk/management-sdk-methods-reference#createsimplefield). | ![Set initial value](/images/docs/api-reference/schema/field-configuration/set-initial-value.png) ### Conditional visibility With conditional visibility, you can show selected fields to editors only when they need them. You can still query the values of hidden fields, as conditional visibility only affects the ability to see a field in the UI. For more information on conditional visibility, see our docs [here](/docs/developer-guides/schema/conditional-fields). ![Conditional visibility inside field details](/images/docs/user-guides/conditional-fields/field-visibility-checkbox.png) ### Field visibility Field visibility has no relation with permissions or security. | UI | Management SDK | | ------------------------------------------------------------------------------- | --------------------------------------------------------------- | | On the **Advanced** tab, use the **Field visibility** dropdown to select an option. | To indicate field visibility, use the `visibility` parameter to pass a type. [Here's an example](/docs/api-reference/management-sdk/management-sdk-methods-reference#updatesimplefield). | The following options are available in the UI: - **Read / Write** - The field will be accessible for read/write operations. Default. - **Read only** - The field will be shown, but cannot be edited in the UI. You can update via the API if required. - **Hidden** - The field will not be shown in the UI, but can be referenced by other fields such as [Slugs](/docs/api-reference/schema/field-types#string). - **API only** - Field is not shown in the UI, but can be used via the API using mutations. You can set field visibility by passing one of the following types in the Management SDK: - `visibility: "READ_WRITE"` - `visibility: "READ_ONLY"` - `visibility: "HIDDEN"` - `visibility: "API_ONLY"` ![Field visibility](/images/docs/api-reference/schema/field-configuration/field-visibility.png) --- # Field types Source: https://hygraph.com/docs/api-reference/schema/field-types ## Overview Your schema is built up of GraphQL types. If you're familiar working with GraphQL, you should feel right at home. Hygraph supports all of the common GraphQL types you are used to, as well as some of its own. You may also be interested in how input types work for filtering, ordering, paginating, and mutating data. Here you will discover the core field types available when building your Hygraph schema. Since your schema is automatically [generated](/docs/api-reference/content-api/queries#auto-generated-queries), it is recommended you browse the [API Playground](/docs/api-reference/basics/api-playground) to get inspect all available field type definitions. ## String Hygraph supports a few variations of the String field type. Strings are just strings, but depending on the variation you add to your model, it will reflect how it appears to content editors. | Variant | Description | | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Single line text | Most used with headings, page titles, email, etc. | | Multi line text | Most used with strings that require no formatting, raw text like HTML, and XML where you control the parsing. | | Markdown | Markdown is most used as an alternative to Rich Text. Enables advanced techniques such as MDX. | | Slug | Slug template with automatic initial value generation based off existing fields. | All 3 variations of the String type are queried in the same way, and return the strings of the field they represent: ```graphql { products { singleLineTextField multiLineTextField markdownField } } ``` ```json { "data": { "products": [ { "singleLineTextField": "Hygraph Mug", "multiLineTextField": "Welcome to Hygraph", "markdownField": "# Hello" } ] } } ``` ## Rich text The `RichText` field type is an advanced String field that returns your content in 4 different formats by default: `raw`, `HTML`, `markdown`, and `text`. `json` is also available when embeds are enabled. The Rich Text field renders an advanced `textarea` with tools to add headings, links, tables, images, lists, etc. If you have [set up multiple values](/docs/api-reference/schema/field-configuration#allow-multiple-values) for Rich Text fields, you cannot [mark the field as required](/docs/api-reference/schema/field-configuration#make-field-required). When a Rich Text field is added to your model, it automatically generates the following types: ```graphql type RichText { raw: RichTextAST! html: String! markdown String! text: String! json: RichTextAST! } ``` Read our dedicated document on [Rich Text](/docs/api-reference/content-api/rich-text-field) for further information. ## Integer Integers are whole numbers, and are often used to reference price in cents, stock quantities etc. For example, here we have products with a Int field for price: ```graphql { products { price } } ``` ```json { "data": { "products": [ { "price": 1000 } ] } } ``` ## Float Floats are floating point numbers, and often represent fractional values. They are often used to describe values with precision, such as distance, weight, volume, etc. For example, here we have products with a Float field for rating: ```graphql { products { rating } } ``` ```json { "data": { "products": [ { "rating": 4.5 } ] } } ``` ## Boolean Booleans default to `null` in Hygraph, and can be `true` or `false`. You may opt to use a Boolean for specifying if a product is on sale, is part of a bundle, or a post accepts comments. For example, here we have posts with a Boolean field for `acceptsComments`: ```graphql { products { acceptsComments } } ``` ```json { "data": { "products": [ { "acceptsComments": true }, { "acceptsComments": false }, { "acceptsComments": null } ] } } ``` ## Date The Date field type adheres to ISO 8601 standard. This means, October 7, 1989 is represented as 1989-10-07. For example, here we have events with a Date for `start`: ```graphql { events { start } } ``` ```json { "data": { "events": [ { "start": "1989-10-07" } ] } } ``` ## Date and time Similar to the date field type, the DateTime field type adheres to ISO 8601 standard. For example, here we have events with a DateTime for `start`: ```graphql { events { start } } ``` ```json { "data": { "events": [ { "start": "1989-10-07T09:30:00+00:00" } ] } } ``` ## JSON Hygraph has native field support for JSON (JavaScript Object Notation). This field is often used for storing large amounts of data from other systems. For example, here we have products with a JSON field for `metadata`: ```graphql { products { metadata } } ``` ```json { "data": { "products": [ { "metadata": { "values": [10, 20, 30], "analyticsId": "ifuhue398" } } ] } } ``` ## Asset Assets are connected to models through a [reference](#reference) field. Assets can be any file type, not just images. The Asset model comes its own default [asset fields](/docs/api-reference/schema/system-fields#asset-fields). For example, here we have posts with a the Asset field for `coverImage`, querying those asset fields: ```graphql { posts { coverImage { url handle fileName height width size mimeType } } } ``` ```json { "data": { "posts": [ { "coverImage": { "url": "https://media.graphassets.com/bh4xr7efSZyBh4iVeGQq", "handle": "bh4xr7efSZyBh4iVeGQq", "fileName": "Examples - Swag Store.png", "height": 720, "width": 1280, "size": 116893, "mimeType": "image/png" } } ] } } ``` ```json { "data": { "ebooks": [ { "file": { "url": "https://media.graphassets.com/pJOCAOncQO6K7azJbagS", "handle": "pJOCAOncQO6K7azJbagS", "fileName": "Hygraph eBook - Navigating Towards Tomorrow-s Content with a Headless CMS.pdf", "height": null, "width": null, "size": 2345835, "mimeType": "application/pdf" } } ] } } ``` Learn more about [Assets](/docs/api-reference/assets/assets-overview). ## Color The Color field is made up of HEX, RGBA and css color values. ```graphql type Color { hex: Hex! rgba: RGBA! css: String! } ``` | Field | Type | Description | | ------ | --------- | ------------------------------------------------------------------- | | `hex` | `Hex!` | Returns a String in the format of `#ffffff` | | `rgba` | `RGBA!` | `r`, `g`, `b`, values as `RGBAHue!`, and `a` as `RGBATransparency!` | | `css` | `String!` | Returns in the format of `rgb(255, 255, 255)` | For example, here is posts with a Color field for `backgroundColor`, in all formats: ```graphql { posts { backgroundColor { hex rgba { r g b a } css } } } ``` ```json { "data": { "posts": [ { "backgroundColor": { "hex": "#ffffff", "rgba": { "r": 255, "g": 255, "b": 255, "a": 1 }, "css": "rgb(255,255,255)" } } ] } } ``` ## Location The Location field type returns `latitude`, `longitude`, and `distance` Float values. ```graphql type Location { latitude: Float! longitude: Float! distance(from: LocationInput!): Float! } ``` | Field | Type | Description | | ----------- | ---------------- | ---------------------------------------------------------- | | `latitude` | `Float!` | Geographic coordinate (north-south position on Earth) | | `longitude` | `Float!` | Geographic coordinate (east-west position on Earth) | | `distance` | `LocationInput!` | Distance in meters `from` the given `latitude`/`longitude` | To query the `distance` field, you must provide `latitude` and `longitude` values for the `from` argument. For example, here we have all shop locations, with distance from the provided latitude/longitude: ```graphql { shops { location { latitude longitude distance( from: { latitude: 50.58153970000001, longitude: 8.665300199999999 } ) } } } ``` ```json { "data": { "shops": [ { "location": { "latitude": 48.7…, "longitude": -122.5…, "longitude": 8.66… } } ] } } ``` ## Enumerations [Enumerations](/docs/api-reference/schema/enumerations), or enum for short, are predefined list of values. They are defined inside your GraphQL schema, and can be referenced by any of your content models. If you have [set up multiple values](/docs/api-reference/schema/field-configuration#allow-multiple-values) for enumerations, you cannot [mark the field as required](/docs/api-reference/schema/field-configuration#make-field-required). For example, here is an enum for products with its commodity type: ```graphql { products { commodity } } ``` ```json { "data": { "products": [ { "commodity": "Digital" } ] } } ``` ## Taxonomies [Taxonomies](/docs/api-reference/schema/taxonomies) are a group of terms arranged in a hierarchical structure. They are defined inside your GraphQL schema, and can be referenced by any of your content models. If you have [set up multiple values](/docs/api-reference/schema/field-configuration#allow-multiple-values) for taxonomies, you cannot [mark the field as required](/docs/api-reference/schema/field-configuration#make-field-required). For example, here is a taxonomy for the `products` model which has a `category` taxonomy field. The category taxonomy field uses the `Clothes` taxonomy. In the GraphQL schema, `category.value` is the taxonomy node attached to the entry, and `category.path` is an array of the full path up to the assigned node. ```graphql query MyQuery { productPage(where: {id: $ProductID}) { category { value path { value } } } } ``` ```json { "data": { "productPage": { "category": { "path": [ { "value": "Clothes" }, { "value": "Women" }, { "value": "Pants" }, { "value": "Casual" } ], "value": "Casual" } } } } ``` ## Reference References, often referred as relations, are a powerful field type that allows you to connect one or more models together, and even reference multiple models as a single field type with GraphQL Union Types. For example, here we have an example of querying all products, with categories they belong to. ```graphql { products { name category { name } } } ``` ```json { "data": { "products": [ { "name": "ACME Hammer", "category": [ { "name": "ACME Products" }, { "name": "Shop Tools" } ] }, { "name": "ACME Shovel", "category": [ { "name": "ACME Products" }, { "name": "Garden Tools" } ] } ] } } ``` ### One-way references One-way references - also called unidirectional relations - only exist in one direction. This type of reference is most useful when there is no need to know where a model is being referenced from, such as a model that is used many times. One-way references only show up on the model for which the reference is configured, and can only be queried from that side as well. This also means that for one-way references, no reverse field is configured on the referenced model. With one-way references, the content editor UI is kept clean by not showing irrelevant relations where they are not needed. One-way references come in two forms: #### To one For example, a category that can have only one product. #### To many For example, a category that can have multiple products. ### Two-way references Two-way references - alternatively known as bidirectional relations - exist in two directions. This type of reference is useful for use cases where both sides of the reference are relevant, and need to be edited or queryable. Two-way references are configured and show up on both the _referencing_ and _referenced_ models, and can be queried from either side. Two-way references come in four forms: #### One to one For example, a category can only have one product, and one product can only have one category. #### One to many For example, a category can have multiple products, but a product cannot belong to multiple categories. #### Many to one For example, a category has one product, but a product can belong to multiple categories. #### Many to many For example, a category can have many products, and products can belong to many categories. ### Union GraphQL Union Types are great for referencing different models as a single field. For example, here we have a typical GraphQL query for fetching `blocks` on a page. This field is configured to be either of type `Hero`, `Grid`, and/or `Gallery`: ```graphql { pages { blocks { __typename ... on Hero { title ctaLink } ... on Grid { title subtitle { markdown } } ... on Gallery { photos { url handle } } } } } ``` Please note that unions are always two-way references. ## Remote fields Remote fields connect specific remote data to an entry of that model. Remote fields are always related to a single remote source, and a single custom type. RESTful remote fields are configured with a path to a specific endpoint in the remote source, such as user details from Github, or price & availability from Shopify. GraphQL remote fields allow to select the entrypoint to the schema (query). ### GraphQL remote field The GraphQL remote field requires a GraphQL Remote Source to be configured on your project. You can find out how to create a Remote Source [here](/docs/developer-guides/remote-data/remote-sources#add-a-remote-source). The Field allows you to make a request (HTTP GET or POST) to a remote GraphQL API and to specify the query entrypoint. [Learn more about adding a remote field to your model](/docs/developer-guides/remote-data/remote-content#add-remote-field) #### Existing field variable in non-string arguments ![Existing field variable in non-string arguments](/images/docs/api-reference/schema/field-types-cast-int.png) In order to support existing field variables in non-string arguments, the `{{!cast=}}` syntax can be used to indicate that the resulting data should be forwarded as is. Please note that there is no post processing done on the data after filling in the existing field variable. Let's say our remote GraphQL API accepts an `int` argument, which we want to get filled in from an `int` field called `n` that already exists on the model we are creating the remote field on. When specifying the template, we can add the handlebars comment `{{!cast=