# Developing a dynamic page extension Dynamic pages let you create pages that load different data based on the URL. Unlike the static pages available in the [Site builder](/frontend-studio/using-the-site-builder.md) area in the Studio, dynamic pages have a dynamic URL. For example, a product details page has a dynamic URL because it contains the product ID and displays the content specific to the matching product ID. ## Dynamic page handler The `dynamic-page-handler` function in the `packages/backend/index.ts` file implements the dynamic page handler extension, which handles requests for all the dynamic pages. If an incoming request URL path is resolved, a `PossibleDynamicPageResults` response is returned. Otherwise, it returns `null`. The [API hub](/frontend-development/architecture-and-stack.md#api-hub) resolves the incoming requests following these rules: - If the path in the request matches a page folder URL, serve the related page folder. - If the dynamic page handler resolves the path in the request, serve the related dynamic page. - If the path in the request doesn't match either a page folder or a dynamic page, return the `404 - Page not found` error. ## Develop a dynamic page To develop a dynamic page, follow these steps: 1. [Create the dynamic page schema in the Studio](/frontend-development/developing-a-dynamic-page-extension.md#create-a-dynamic-page-schema). 2. [Implement the dynamic page handler](/frontend-development/developing-a-dynamic-page-extension.md#implement-the-dynamic-page-logic). ### Create a dynamic page schema A dynamic page schema specifies the information about a dynamic page and its configuration. The schema determines the configuration options displayed in the [**Dynamic pages**](/frontend-studio/dynamic-pages.md) area of the Studio. We recommend that you use our visual editor in the schema builder because it provides all the fields and options you need to input. A dynamic page schema requires the following fields: - `dynamicPageType`- String - Required. It must comply with the format `COMPANY_OR_PROJECT_NAME/DYNAMIC_PAGE_IDENTIFIER`. - `name` - String - Required. It specifies how the dynamic page is referenced in the Studio and should be understandable for Studio users. The category and icon are specific schema fields for visualization in the Studio. - `category` - String - Optional. It specifies the clustering of the dynamic page in the Studio. - `icon` - String - Optional. It specifies the name of the icon associated with the dynamic page in Studio. - `dataSourceType` - String - Required. It specifies the name of the main [data source](/frontend-development/development-concepts.md#data-sources) for the dynamic page. Every dynamic page of the specified dynamicPageType will contain a data source of this type, which provides the data that the page needs. - `isMultiple` - Boolean - Required. If `true`, the dynamic page supports creating [dynamic page rules](/frontend-development/developing-a-dynamic-page-extension.md#dynamic-page-rule). Otherwise, `false`. - `pageMatchingPayloadSchema` - Array of objects - Optional. It contains a list of [schema fields](/frontend-development/schemas.md#schema-field-types) that are displayed in the Studio to set dynamic page rule criteria with the [dynamic page rule builder](/frontend-studio/using-dynamic-pages.md#page-rule-criteria-builder). The `pageMatchingPayloadSchema` only supports the following schema field types: [boolean](/frontend-development/schemas.md#boolean), [enum](/frontend-development/schemas.md#enum), [numeric](/frontend-development/schemas.md#number-integer-and-decimal), [string](/frontend-development/schemas.md#string), and [dynamic-filter](/frontend-development/schemas.md#dynamic-filter). All other field types are displayed as `string` fields on the Studio. In the following example, we create a `product/product-blog-page` dynamic page that uses the `product/product-blog` data source, supports [dynamic page rules](/frontend-development/developing-a-dynamic-page-extension.md#dynamic-page-rule), and defines the `blog.id` and `blog.type` fields for setting the dynamic page rule criteria in the Studio. ```json title="Example dynamic page schema for a product blog with static fields" { "dynamicPageType": "product/product-blog-page", "name": "Product blog", "category": "Blog", "icon": "stars", "dataSourceType": "product/product-blog", "isMultiple": true, "pageMatchingPayloadSchema": [ { "label": "Blog ID", "field": "blog.id", "type": "string" }, { "label": "Blog type", "field": "blog.type", "type": "enum", "values": [ { "value": "comparison", "name": "comparison" }, { "value": "trends", "name": "trends" } ] } ] } ``` In this example, the `blog.type` field values might require frequent updates. In such case, you can instead use the [dynamic-filter](/frontend-development/schemas.md#dynamic-filter) field type and create an action extension that returns the schema fields dynamically. ```json title="Example dynamic page schema for a product blog with dynamic filter" { "dynamicPageType": "product/product-blog-page", "name": "Product blog", "category": "", "icon": "stars", "dataSourceType": "product/product-blog", "isMultiple": true, "pageMatchingPayloadSchema": [ { "label": "Blog dynamic filter fields", "field": "dynamicFields", "type": "dynamic-filter", "dynamicFilterEndpoint": "/frontastic/action/product-blog/dynamic-fields" } ] } ``` You can store the schema `JSON` file where you prefer because it is only required in the Studio and not in your Frontend project code. ### Upload the dynamic page schema to the Studio To upload a dynamic page JSON schema, follow these steps: 1. From the Studio home page or from the left menu, go to **Developer > Dynamic pages**. 2. Click **Upload schema** to browse and upload a JSON file you created or click **Create schema**: the [schema editor](/frontend-studio/using-the-schema-builder) opens. 3. From the schema editor, click **Publish** to make the dynamic page available in the [**Dynamic pages** area](/frontend-studio/using-dynamic-pages). Otherwise, click **Save as draft**. ### Implement the dynamic page logic The dynamic page handler extension is a function in the `packages/backend/index.ts` that handles the logic for dynamic pages. This gives you the flexibility to implement the routing logic that best suits your needs. For example, in the following code, we use a regular expression (`/\/blog\/(.+)\/(\d+)/gm`) to match paths starting with `/blog` containing a slug and blog ID. If the URL matches the expression pattern, the `dynamic-page-handler` returns the corresponding blog content in a `DynamicPageSuccessResult`, which contains: - `dynamicPageType`: used to identify the required dynamic page layout. - `dataSourcePayload`: relayed to the page, it contains data from a data source to be displayed on the page in Frontend components. - `pageMatchingPayload`: payload for the [dynamic page rule](/frontend-development/developing-a-dynamic-page-extension.md#dynamic-page-rule) criteria in the Studio. If empty, the data in `dataSourcePayload` is used for the dynamic page rule criteria. In the following example, we implement the dynamic page handler for the `product/product-blog` dynamic page. Executing API calls costs time and affects the performance of your website. Therefore, you should make as few calls as possible and execute them in parallel. ```typescript title="Dynamic page handler for the product blog dynamic page" export default { 'dynamic-page-handler': async ( request: Request ): Promise => { // Example implementation const matchesProductBlogPage = request.query.path.match(/\/blog\/(.+)\/(\d+)/gm); if (matchesProductBlogPage) { const [_, _blog, slug, blogID] = matchesProductBlogPage[0].split('/'); // Example implementation const data = await getBlogContent(blogId); return { dynamicPageType: 'product/product-blog', dataSourcePayload: response.data, pageMatchingPayload: { blog: { id: response.data.id, type: response.data.type, }, }, }; } // Implement the logic of another dynamic page. return null; }, // ... }; ``` #### Dynamic page rule For your dynamic pages, you can also [set dynamic page rules](/frontend-studio/using-dynamic-pages#dynamic-page-rules) to display a specific page version if the rule's criteria are met. For the same dynamic page, you can create both a default page version and page versions depending on dynamic page rules. For example, for a product details page, you can create a default page version with a generic layout and a page version with a *special offer* layout that appears if the selected product is under 10 Euros. For further information, see [Page rule criteria builder](/frontend-studio/using-dynamic-pages#page-rule-criteria-builder). If, for the same dynamic page, you set different dynamic page rules with the related page versions and an item matches the criteria of multiple rules, it is not possible to determine which page version displays on your website. The `pageMatchingPayload` structure must conform with the fields in the `pageMatchingPayloadSchema` for the Studio to display the configurable options correctly. Do not use arrays in the return structure of `pageMatchingPayload`. The dynamic page rule matching system is designed for objects and flat key-value pairs and cannot process array-based criteria. #### Add links to dynamic pages commercetools Frontend doesn't know the URL structure you implement in the dynamic page handler extension. Hence, it is not possible to generate links for dynamic pages automatically. You must handle the linking for dynamic pages either [during client-side routing](/frontend-development/client-side-routing-for-dynamic-pages.md) or by generating URLs for linking on the backend. For example, to use backend-generated URLs for the `product/product-blog` dynamic page, implement a function like the following and return the generated URL as a `_url` parameter from all the blog-related data sources, and then use the `_url` in your Frontend components to link to the blog pages. ```typescript title="Generating dynamic page URLs on backend" const getProductBlogURL = async (blogID: string): Promise => { const blogData = await getBlogData(blogID); return `/blog/${blogData.slug}/${blogData.id}`; }; ``` In case internationalized URLs are required, the convention is to use the property `_urls` which holds a hash-map mapping locale strings to URL paths. For example: `{ _urls: { 'en_US': '/blog/this-is-the-best-product/23', 'de_DE': '/blog/das-ist-das-beste-produkt/23' }`. #### Redirect from a dynamic page Apart from the `DynamicPageSuccessResult`, you can also return a `DynamicPageRedirectResult` for scenarios where the current page has been moved to a new URL or the page doesn't exist anymore. In the following example, we update the `product/product-blog` dynamic page handler to redirect to the new page if the blog slug has changed. ```typescript title="Example implementation of a redirect response" export default { 'dynamic-page-handler': async ( request: Request ): Promise => { // example implementation const matchesProductBlogPage = request.query.path.match(/\/blog\/(.+)\/(\d+)/gm); if (matchesProductBlogPage) { const [_, _blog, slug, blogID] = matchesProductBlogPage[0].split('/'); // example implementation const data = await getBlogContent(blogId); if (data.slug !== slug) { // if the slug has changed, redirect to the new URL return { redirectLocation: getProductBlogURL(blogId), statusCode: 301, }; } else { return { dynamicPageType: 'product/product-blog', dataSourcePayload: response.data, pageMatchingPayload: { blog: { id: response.data.id, type: response.data.type, }, }, }; } } // implement other dynamic pages logic return null; }, }; ``` ## Related pages - [Area overview page with navigation](/frontend-development.md) - [Previous page: Developing an action extension](/frontend-development/developing-an-action-extension.md) - [Next page: Dynamic configuration from the browser](/frontend-development/dynamic-configuration-from-the-browser.md)