Page addition

Add new pages with pre-populated elements to designs.

Running this example

To run this example locally:

  1. If you haven't already, create a new app in the Developer Portal(opens in a new tab or window). For more information, refer to our Quickstart guide.

  2. In your app's configuration on the Developer Portal(opens in a new tab or window), ensure the "Development URL" is set to http://localhost:8080.

  3. Clone the starter kit:

    git clone https://github.com/canva-sdks/canva-apps-sdk-starter-kit.git
    cd canva-apps-sdk-starter-kit
    SHELL
  4. Install dependencies:

    npm install
    SHELL
  5. Run the example:

    npm run start page_addition
    SHELL
  6. Click the Preview URL link shown in the terminal to open the example in the Canva editor.

Example app source code

// For usage information, see the README.md file.
import { Alert, Button, Rows, Text } from "@canva/app-ui-kit";
import type { Dimensions, EmbedElement, GroupElement } from "@canva/design";
import { addPage, getDesignMetadata } from "@canva/design";
import { CanvaError } from "@canva/error";
/**
* Static images are used here for demonstration purposes only.
* In a real app, you should use a CDN/hosting service to host your images,
* then upload them to Canva using the `upload` function from the `@canva/asset` package.
*/
/* eslint-disable-next-line no-restricted-imports */
import weather from "assets/images/weather.png";
import { useState, useEffect } from "react";
import * as styles from "styles/components.css";
import { upload } from "@canva/asset";
import { useFeatureSupport } from "utils/use_feature_support";
const IMAGE_ELEMENT_WIDTH = 50;
const IMAGE_ELEMENT_HEIGHT = 50;
const TEXT_ELEMENT_WIDTH = 130;
const HEADER_ELEMENT_SCALE_FACTOR = 0.2;
const EMBED_ELEMENT_SCALE_FACTOR = 0.4;
const createHeaderElement = async (): Promise<GroupElement> => {
const { ref } = await upload({
mimeType: "image/png",
thumbnailUrl: weather,
type: "image",
url: weather,
width: 100,
height: 100,
aiDisclosure: "none",
});
return {
type: "group",
children: [
{
type: "image",
ref,
top: 0,
left: 0,
width: IMAGE_ELEMENT_WIDTH,
height: IMAGE_ELEMENT_HEIGHT,
altText: {
text: "weather forecast photo",
decorative: undefined,
},
},
{
type: "text",
children: ["Weather Forecast"],
top: IMAGE_ELEMENT_HEIGHT,
left: IMAGE_ELEMENT_WIDTH / 2 - TEXT_ELEMENT_WIDTH / 2,
width: TEXT_ELEMENT_WIDTH,
},
],
};
};
const embedElement: EmbedElement = {
type: "embed",
url: "https://www.youtube.com/watch?v=tBe79N-4zm4",
};
export const App = () => {
const [error, setError] = useState<string | undefined>();
const [isLoading, setIsLoading] = useState(false);
const [defaultPageDimensions, setDefaultPageDimensions] = useState<
Dimensions | undefined
>();
const isSupported = useFeatureSupport();
const isRequiredFeatureSupported = isSupported(addPage);
useEffect(() => {
getDesignMetadata().then(({ defaultPageDimensions }) => {
// Dimensions are undefined if the user is in an unbounded design (e.g. Whiteboard).
if (!defaultPageDimensions) {
setError(
"Adding pages in unbounded documents, such as Whiteboards, is not supported.",
);
}
setDefaultPageDimensions(defaultPageDimensions);
});
}, []);
const addNewPage = async () => {
setIsLoading(true);
try {
// Dimensions are undefined if the user is in an unbounded design (e.g. Whiteboard).
if (!defaultPageDimensions) {
return;
}
setError(undefined);
const headerElementWidth =
defaultPageDimensions.width * HEADER_ELEMENT_SCALE_FACTOR;
const embedElementWidth =
defaultPageDimensions.width * EMBED_ELEMENT_SCALE_FACTOR;
const headerElement = await createHeaderElement();
await addPage({
title: "Weather forecast",
elements: [
{
...headerElement,
width: headerElementWidth,
height: "auto",
// Shift from the top by 10%
top: defaultPageDimensions.height * 0.1,
// Shift it by 50% of the page width, then subtract 50% of the group element width.
left: defaultPageDimensions.width / 2 - headerElementWidth / 2,
},
{
...embedElement,
width: embedElementWidth,
height: "auto",
// Shift from the top by 40%
top: defaultPageDimensions.height * 0.4,
// Shift it by 50% of the page width, then subtract 50% of the group element width.
left: defaultPageDimensions.width / 2 - embedElementWidth / 2,
},
],
});
setError(undefined);
} catch (e) {
if (e instanceof CanvaError) {
switch (e.code) {
case "quota_exceeded":
setError(
"Sorry, you cannot add any more pages. Please remove an existing page and try again.",
);
break;
case "rate_limited":
setError(
"Sorry, you can only add up to 3 pages per second. Please try again.",
);
break;
default:
setError(e.message);
break;
}
}
} finally {
setIsLoading(false);
}
};
return (
<div className={styles.scrollContainer}>
<Rows spacing="3u">
<Text>
This example demonstrates how apps can add a new page with
pre-populated elements.
</Text>
{error && <Text tone="critical">{error}</Text>}
<Button
variant="primary"
onClick={addNewPage}
stretch
// Default page dimensions are undefined in unbounded designs, so the button remains disabled.
// Add page is not supported in certain design type such as docs.
disabled={!defaultPageDimensions || !isRequiredFeatureSupported}
loading={isLoading}
>
Add page
</Button>
{!isRequiredFeatureSupported && <UnsupportedAlert />}
</Rows>
</div>
);
};
const UnsupportedAlert = () => (
<Alert tone="warn">
Sorry, the required features are not supported in the current design.
</Alert>
);
TYPESCRIPT
import { AppUiProvider } from "@canva/app-ui-kit";
import { createRoot } from "react-dom/client";
import { App } from "./app";
import "@canva/app-ui-kit/styles.css";
const root = createRoot(document.getElementById("root") as Element);
function render() {
root.render(
<AppUiProvider>
<App />
</AppUiProvider>,
);
}
render();
if (module.hot) {
module.hot.accept("./app", render);
}
TYPESCRIPT
# Page addition
Demonstrates how to add new pages to designs with pre-populated content including images, text, and embed elements. Shows page creation, content layout, and multi-element page composition.
For API reference docs and instructions on running this example, see: https://www.canva.dev/docs/apps/examples/page-addition/.
Related examples: See design_interaction/design_editing for page content manipulation, or design_elements examples for individual element creation patterns.
NOTE: This example differs from what is expected for public apps to pass a Canva review:
- Static assets are used for demonstration purposes only. Production apps should host assets on a CDN/hosting service and use the `upload` function from the `@canva/asset` package
- ESLint rule `no-restricted-imports` is disabled for example purposes only. Production apps should not disable linting rules without proper justification
- Error handling is simplified for demonstration. Production apps must implement comprehensive error handling with clear user feedback and graceful failure modes
- Internationalization is not implemented. Production apps must support multiple languages using the `@canva/app-i18n-kit` package to pass Canva review requirements
MARKDOWN

API Reference

Need Help?