# Hygraph documentation — API Reference Source: https://hygraph.com/docs/api-reference See https://hygraph.com/llms.txt for the curated index. --- # 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-unauthenticated-access-to-the-content-api) ### 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#project-and-governance). ## 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/getting-started/access-and-permissions/api-access#authenticated-requests-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#authenticated-requests-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=