Skip to content

App Components

Searchable List View

How to use the SearchableListView component.

SearchableListView is a UI component in the Apps SDK that helps users find your images, videos, embeds, and audio. It shows previews of your resources, and includes a search bar and customizable list filters.

You can further customize this view, changing the container type, layout, and search options, among others. When a user clicks on a result, your app can trigger additional actions, such as adding the item to a design.

The following sections demonstrate some of the main features of SearchableListView, with examples of how you can customize them further.

Filter options

You can define the filters that let users narrow their search to specific filetypes:

ts
{
  filterType: "CHECKBOX",
  label: "File Type",
  key: "fileType",
  options: [
    { value: "mp4", label: "MP4" },
    { value: "png", label: "PNG" },
    { value: "jpeg", label: "JPEG" },
  ],
  allowCustomValue: true,
},

Example:

Sort options

You can also define the sort options that users see.

typescript
sortOptions: [
  { value: "created_at DESC", label: "Creation date (newest)" },
  { value: "created_at ASC", label: "Creation date (oldest)" },
  { value: "updated_at DESC", label: "Updated (newest)" },
  { value: "updated_at ASC", label: "Updated (oldest)" },
  { value: "name ASC", label: "Name (A-Z)" },
  { value: "name DESC", label: "Name (Z-A)" },
],

Example:

Layout

You can define how results are presented to users, using a list, masonry, or full width layout.

List

This option presents the results in a list view:

typescript
layouts: ["LIST"],

Example:

Masonry

This option presents the results in a masonry layout:

typescript
layouts: ["MASONRY"],

Example:

For a working example, see the Giphy app.

Full width

This option presents the results in a full width layout:

typescript
layouts: ["FULL_WIDTH"],

Example:

For a working example, see the YouTube app.

Next steps

  • For the full list of SearchableListView customization options and their combinations, see the reference documentation.
  • To add infinite scrolling to the SearchableListView component, see the example below.

Example: Infinite scrolling

To help resources load quickly, the SearchableListView component limits how many resources can be returned in a single response.

If your app offers more resources than this limit allows, you can use multiple requests to load additional resources. This is commonly known as pagination and the Apps SDK calls it continuation.

This article explains how to enable infinite scrolling for the SearchableListView component, using an example digital asset management app. This app is maintained by Canva and already has pagination configured.

  1. The backend retrieves images from Lorem Picsum, a service that provides placeholder photos.
  2. The frontend uses the SearchableListView component to present the resources to users.
  3. To retrieve the next page of resources, the backend uses the continuation property.

Step 1: Configure the service

  1. In your backend code, create a basic Node.js server (using Express), and import modules for handling the Canva app components and HTTP requests:

    typescript
    import {
      FindResourcesRequest,
      FindResourcesResponse,
      Image,
    } from "@canva/app-components";
    import axios from "axios";
    import express from "express";
    import cors from "cors";
    
    const app = express();
    
    app.use(express.json());
    app.use(express.static("public"));
    app.use(cors());

Step 2: Load the first page of resources

To retrieve the resources that will appear in the side panel, SearchableListView sends a POST request to the location you defined in findResources. This example uses /content/resources/find.

Example frontend code:

typescript
        findResources={async (request) => {
          const response = await fetch(
            "http://localhost:3000/content/resources/find",
            {
              method: "POST",
              headers: {
                "Content-Type": "application/json",
              },
              body: JSON.stringify(request),
            }
          );

Example backend code:

typescript
  app.post("/content/resources/find", async (req, res) => {
    const findResourcesRequest: FindResourcesRequest = req.body;
    const currentPage = findResourcesRequest.continuation || "1";

The following steps demonstrate how to retrieve the first page of resources and format the results.

  1. To retrieve a list of images from Lorem Picsum, send a GET request to this URL:

    bash

    The response contains an array of images, with each image represented in the following structure:

    json
    {
      "id": "0",
      "author": "Example author",
      "width": 5600,
      "height": 3700,
      "url": "https://unsplash.com/...",
      "download_url": "https://picsum.photos/..."
    }
  2. To retrieve a different page of images, append a page parameter to the URL:

    bash
  3. To perform the request, call the request method:

    typescript
    const options = {
      url: "https://picsum.photos/v2/list",
      params: {
        page: currentPage,
      },
    };
    
    const picsum = await axios.request(options);
  4. Convert the data into a format that Canva can process:

    typescript
    const images: Image[] = picsum.data.map((image: any) => {
      return {
        type: "IMAGE",
        id: image.id,
        name: `Photo by ${image.author}`,
        url: image.download_url,
        thumbnail: {
          url: image.download_url,
        },
        contentType: "image/jpeg",
      };
    });

Step 3: Load the next page

Before a user reaches the end of the resource list, you can trigger an additional request to /content/resources/find. This lets you seamlessly load additional resources, creating the effect of infinite scrolling.

You can do this by adding a continuation property to the response. This example shows the continuation property with the nextPage value, which loads the next page of results once the first page has loaded. This value doesn't have to be a page number, and only needs to be a string.

typescript
    const nextPage = (parseInt(currentPage, 10) + 1).toString();
    const findResourcesResponse: FindResourcesResponse = {
      type: "SUCCESS",
      resources: images,
      continuation: nextPage,
    };
    res.send(findResourcesResponse);
  });

This property tells SearchableListView to send another POST request to /content/resources/find, before the user reaches the end of the list of resources. This property and its value is included in the request body.

After adding this change, scroll through the resources in the side panel. You'll notice that the same resources are loaded repeatedly, which is addressed in the following steps.

Step 4: Get the current page number

When a response contains a continuation property, that same continuation property is available in the body of the next POST request that's sent to /content/resources/find.

You can check this behavior by logging the value of findResourcesRequest.continuation:

typescript
console.log(findResourcesRequest.continuation);

When the first page of resources is loaded, there isn't a previous response because the continuation property is null. However, if you trigger another request by scrolling through the resources, the "2" value is logged. You can use this behavior to load unique resources for each request.

At the top of the route (before the options object), create a currentPage variable containing the value of the request body's continuation property. In addition, provide a fall-back value of "1" because the continuation property is null on the initial request.

typescript
const currentPage = findResourcesRequest.continuation || "1";

You can then update the page parameter in the options object to use the currentPage variable:

typescript
const options = {
  url: "https://picsum.photos/v2/list",
  params: {
    page: currentPage,
  },
};

As a result of these changes, you can load the first and second pages of content into the side panel.

As you continue scrolling through the resources, you'll notice that the second page of images is repeatedly loaded. This occurs because the value of the continuation property is hard-coded as "2" in all responses; This is addressed in the following steps.

Step 5: Increment the page number

To load unique resources for every request, you need to increment the value of the continuation property.

Before the res.send method, create a variable that:

  1. Parses the number from the currentPage variable.
  2. Increments the page number by one.
  3. Converts the result back into a string.

For example:

typescript
const nextPage = (parseInt(currentPage, 10) + 1).toString();

You can then provide this variable in the response:

typescript
  const findResourcesResponse: FindResourcesResponse = {
	type: "SUCCESS",
	resources: images,
	continuation: nextPage,
  }
  res.send(findResourcesResponse);
});

As a result, a unique set of resources is loaded for each request.

Step 6: Load the final page of resources

When reaching the final page of resources, don't provide a continuation property in the response:

typescript
res.send({
  type: "SUCCESS",
  resources: images,
});

Alternatively, set the continuation property to null, which indicates that there are no more pages to load:

typescript
res.send({
  type: "SUCCESS",
  resources: images,
  continuation: null,
});

Example backend code

Find the complete example for the backend below:

typescript
import {
  FindResourcesRequest,
  FindResourcesResponse,
  Image,
} from "@canva/app-components";
import axios from "axios";
import express from "express";
import cors from "cors";

const app = express();

app.use(express.json());
app.use(express.static("public"));
app.use(cors());

app.post("/content/resources/find", async (req, res) => {
  const findResourcesRequest: FindResourcesRequest = req.body;
  const currentPage = findResourcesRequest.continuation || "1";

  const options = {
    url: "https://picsum.photos/v2/list",
    params: {
      page: currentPage,
    },
  };

  const picsum = await axios.request(options);

  const images: Image[] = picsum.data.map((image: any) => {
    return {
      type: "IMAGE",
      id: image.id,
      name: `Photo by ${image.author}`,
      url: image.download_url,
      thumbnail: {
        url: image.download_url,
      },
      contentType: "image/jpeg",
    };
  });

  const nextPage = (parseInt(currentPage, 10) + 1).toString();
  const findResourcesResponse: FindResourcesResponse = {
    type: "SUCCESS",
    resources: images,
    continuation: nextPage,
  };
  res.send(findResourcesResponse);
});

app.listen(3000);

Localization

The user-facing strings in the SearchableListView component will be automatically localized for all supported locales as long as your app has been otherwise localized in accordance with the recommended localization workflow.

Export

Use the saveExportedDesign callback to export a Canva design to a third-party platform. You can do this by defining a function that uses saveExportedDesign, and setting your configuration to accept exports.

To export a Canva design to a third-party platform:

  1. Enable exports in the SearchableListView config.ts file:

    ts
    {
      export: {
        enabled: true,
        estimatedUploadTimeMs: 1500,
        acceptedFileTypes: ["gif", "jpg"]
      }
    }

    The available options for the export property include:

    • enabled: Set as true to enable exports.
    • acceptedFileTypes: Include a list of file types that can be exported.
    • requireContainerSelected: Set as true if you require users to select a container for the design before they can export it.
    • containerTypes: Specify how the export container type is presented and managed (for example, a "folder" or "collection").
    • estimatedUploadTimeMs: Set a value in milliseconds to adjust progress bar appearance and speed.
  2. Add a saveExportedDesign prop to the component:

    tsx
    <SearchableListView
      config={config}
      findResources={findResources}
      saveExportedDesign={(
        exportedDesignUrl: string,
        containerId: string | undefined,
        designTitle: string | undefined
      ) => {
        return new Promise((resolve) => {
          setTimeout(() => {
            console.info(
              `Saving file "${designTitle}" from ${exportedDesignUrl}
                     to ${config.serviceName}
                     container id: ${containerId}`
            );
            resolve({ success: true });
          }, 1000);
        });
      }}
    />

This prop accepts a callback function that runs when the design has finished exporting. You can use this function to:

  • Download the exported design file.
  • Save the file to your service.

To see an example of how this callback is used, check out the DAM app template.

Example:

Changelog

The latest changes to the App Components.

2.4.0 - 2026-04-20

🔧 Changed

  • Updated OauthAccountSwitcher so onAccountSwitch receives null when there is no active account (after the last account was disconnected).

2.3.0 - 2026-04-02

🧰 Added

  • Added AccountSwitcher (controlled) and OauthAccountSwitcher (self-contained) composite components for multi-account Oauth flows. AccountSwitcher accepts accounts and callbacks as props for full developer control. OauthAccountSwitcher wraps it and accepts a MultiAccountOauth client from @canva/user, managing account loading, switching, adding, and disconnection internally. Added @canva/user as a peer dependency.

2.2.0 - 2026-03-16

🔧 Changed

  • Bumped peer dependency @canva/app-ui-kit to ^5.7.0

2.1.0 - 2025-10-20

🐞 Fixed

  • Fixed the hover styling for item cards.
  • Fixed the footer button background in dark mode.

🔧 Changed

  • Upgraded @canva/app-ui-kit to version 5.1.0, see @canva/app-ui-kit changelogs here.
  • Upgraded react from ^19.0.0 to ^19.2.0
  • Upgraded react-dom from ``^19.0.0to^19.2.0`

2.0.0 - 2025-10-01

🔧 Changed

  • Upgraded @canva/app-ui-kit to version 5, see @canva/app-ui-kit changelogs here.
  • Upgraded react from ^18.3.1 to ^19.0.0
  • Upgraded react-dom from ``^18.3.1to^19.0.0`
  • Upgraded react-intl from 6.6.8 to 7.1.11
  • Upgraded peer dependencies for use with react v19

1.3.1 - 2025-07-24

🔧 Changed

  • Upgraded @canva/design from version ^2.4.1 to ^2.7.0.
  • Upgraded @canva/platform from version ^2.1.0 to ^2.2.0.

1.3.0 - 2025-04-22

🧰 Added

  • Added thumbnailCorsCheck option to Config.

    When user drags and drops an asset from the app to their Canva design, Canva will check if the thumbnail is CORS enabled to allow www.canva.com before showing the thumbnail as a drag-and-drop preview. It is a common issue that thumbnail images don't have CORS enabled, so since version 1.0.0, SearchableListView always uses best effort to check if the thumbnail is CORS enabled by sending a pre-flight request for the thumbnail with crossOrigin: "anonymous", and replaces the thumbnail with a fallback image if this request fails. However, this pre-flight request origin will be the app frontend (app-&lt;app-id&gt;.canva-apps.com), so it is not an accurate check if the thumbnail only enables CORS for www.canva.com. If your thumbnail only allows www.canva.com as a CORS origin, you can disable the CORS check by setting thumbnailCorsCheck to {enabled: false}.

🐞 Fixed

  • Fixed the issue that when user tries to drag an oversized asset into Canva design, the asset thumbnail disappears from the list.
  • Removed the usage of deprecated ui.startDrag API.

🔧 Changed

  • Upgraded @canva/asset from 2.0.0 to 2.2.0
  • Upgraded @canva/design from 2.2.1 to 2.4.1
  • Upgraded @canva/error from 2.0.0 to 2.1.0
  • Upgraded @canva/platform from 2.0.0 to 2.1.0

1.2.0 - 2025-03-24

🐞 Fixed

  • Fixed image or video thumbnail overflowing from the card in list layout

🔧 Changed

  • Upgraded @canva/app-ui-kit to version 4.8.0, see @canva/app-ui-kit changelogs here.
  • Upgraded react from ^18.1.0 to ^18.3.1
  • Upgraded react-dom from ^18.1.0 to ^18.3.1

1.1.0 - 2024-12-02

🧰 Added

  • If the size is larger than the asset SDK limit, explain to user that they cannot upload the asset directly into Canva, and suggest user to download the file in a toast notification. Please provide a fileSizeKB field for Image Video and Audio types if you have the file size information, as this will help reduce failed upload SDK calls.

🐞 Fixed

  • Fix toast notification not displayed when export has succeeded or failed.

🔧 Changed

  • When Image or Video do not have a valid thumbnail url, show an image or video icon in the place of the thumbnail.

1.0.0 - 2024-11-25

🧰 Added

  • Added translation (i18n) system to SearchableListView

🔧 Changed

  • Changed toast notification position from overlapping the app content to appearing above the content, when design export fails or success.

1.0.0-beta.32 - 2024-10-31

🧰 Added

  • Exported Thumbnail type that's used for thumbnail field of Image, Video, Embed, Audio and Container.

🔧 Changed

  • Remove dependency on @radix-ui/react-tabs, use Tabs component from @canva/app-ui-kit instead.
  • Hide tabs that contain no results.

🐞 Fixed

  • Fixed not continuing to call findResourcesCallback if the previous page has a non-empty continuation but contains only "UNKNOWN" resources.

1.0.0-beta.30 - 2024-10-24

🔨 Breaking changes

  • Excluded "application/json" option from accepted VideoMimeType, as it's only accepted by @canva/assets for Lottie files and DAM template does not support Lottie files yet.
  • Removed visibility field from Container.

🧰 Added

  • Added access field to Container, if it's set to "read-only", user will not be able to click the "Save to ..." button from this container, or choose this container in the export page.
  • Added support to select from the hierarchy of sub-containers when saving an exported Canva design.
  • Allow saveExportedDesign callback to include an optional errorMessage field in the returned object. If and errorMessage is provided when export failed, the message will be displayed in the alert message.

1.0.0-beta.29 - 2024-10-03

🧰 Added

  • Added export.requireContainerSelected field (default true) for Config to control whether user has to select a container before exporting their design.

🔧 Changed

  • Use @canva/app-ui-kit components DateInput and Flyout in the filter form.
  • Use ImageMimeType VideoMimeType and AudioMimeType from @canva/asset package, instead of defining them locally.
  • When user is browsing on a tab (e.g. Assets) and starts a search, when showing search results, keep user on the tab they were originally at.
  • Upgraded @canva/design from 2.0.0 to 2.1.0.

1.0.0-beta.28 - 2024-09-24

🔨 Breaking changes

  • acceptedFileTypes for config.export has changed from Array&lt;"PNG" | "JPG" | "PDF_STANDARD" | "VIDEO" | "GIF" | "PPTX" | "SVG"&gt; to Array&lt;"png" | "jpg" | "pdf_standard" | "video" | "gif" | "pptx" | "svg"&gt; to adapt to @canva/asset v2 ExportFileType type.

🔧 Changed

  • Upgraded @canva/asset, @canva/design @canva/platform and canva/error to version 2.0.0.
  • Updated @canva/app-ui-kit version to 4.0.0 (changelog)

1.0.0-beta.26 - 2024-09-19

🧰 Added

  • The thumbnail of image, video and embed assets would normally be used as preview images for when:

    • user is dragging the asset into their design, and
    • the asset is being uploaded.

    If the thumbnail image is blocked by CORS, the preview will look broken. In this version, we added a check on whether the thumbnail image can be displayed. If not, we will provide a fallback preview image.

🐞 Fixed

  • Fixed container name position not centralized vertically.

1.0.0-beta.25 - 2024-09-09

🐞 Fixed

  • Remove unnecessary invocations of getUrl.

1.0.0-beta.24 - 2024-09-05

🐞 Fixed

  • Fixed container thumbnail image squeezed narrow when the container's name is long.

1.0.0-beta.23 - 2024-08-27

🧰 Added

  • Allow the Image, Audio and Video listed in FindResourcesResponse to not provide a url. When the resource does not have a url, it has to provide a getUrl: () =&gt; Promise&lt;string&gt; callback that when invoked, resolves to the URL. This is useful if you need to take extra steps to decide the URL of asset you are trying to upload, for example, if you need to make an additional HTTPS request to fetch the URL.

🔧 Changed

  • If the durationMs for Audio is 0 or unspecified, the template will try to detect the actual duration of the audio by inspecting the audio metadata in DOM.

1.0.0-beta.22 - 2024-08-13

🧰 Added

  • Add an additional parameter, designTitle?: string to saveExportedDesign. The designTitle will be the title of exported design, if it has been set by user.

1.0.0-beta.21 - 2024-07-23

🐞 Fixed

  • Fixed attachment badge being stretched on Firefox and Safari.

🔧 Changed

  • If enableAppStatePersistence is turned on and user last navigated to the export design page, do not take user back to export page when they open the app again. Instead take user to the page before export.

1.0.0-beta.20 - 2024-07-08

Added

  • Added dragAndDropPreview to Image, Video and Embed resource types. It can be used to specify a fall-back image for preview when user is dragging an asset to the design, when the image at thumbnail.url does not allow canva.com as an origin.
  • Trigger a refetch of resources when user has successfully exported their Canva design, so that the newly exported design will be reflected.
  • Allow user to click the tags on the item details page to trigger a search:
    • if the app supports filtering by tag and the current tag is available as an option, start a search by filtering with this tag;
    • if the app does not support filtering by tag or the current tag is not an available option, start a keyword search with the tag's text label.

🐞 Fixed

  • Ensure the page has a bottom padding of 16px when there are no more resources to be loaded.
  • Ensure user can still navigate to the asset details page when the id contains special character.

1.0.0-beta.19 - 2024-06-18

🧰 Added

  • Added tab field in FindResourcesRequest to indicate which tab user is currently at, so that the app backend can choose to provide different resources for different tab, even when multiple tabs are showing the same resource type.
  • Added tabs field for ContainerType, so that each container type can have their customized tabs.

🐞 Fixed

  • Fix typo for "Asset" -> "Assets" tab label inside containers.
  • Allow apps to display the more info bubble even when there are no tabs.

🔧 Changed

  • Changed tabValues field for OVERVIEW tab optional, and default to all tabs on the current page.

1.0.0-beta.18 - 2024-05-30

🔧 Changed

  • Updated @canva/app-ui-kit version to 3.5.1 (changelog)

🐞 Fixed

  • Fixed search menu overlapping tabs when tab carousel appears

1.0.0-beta.17 - 2024-05-03

🔧 Changed

  • Reduced the top space of back buttons to give more space for content.

🐞 Fixed

  • Fixed export requested multiple times if no container needs to be selected for export.

1.0.0-beta.16 - 2024-04-26

🧰 Added

  • Added support for customizable tabs.
  • Added support for an optional exit button.

🔧 Changed

  • Improve mobile experience with filter form.

1.0.0-beta.15 - 2024-04-11

🔧 Changed

  • Improved the drag-and-drop experience in list layout by making the whole list item draggable.
  • Restrict the maximum width for the preview image for drag-and-drop to 200px.

🐞 Fixed

  • Fixed item details button not visible enough in light mode.

1.0.0-beta.14 - 2024-04-09

🧰 Added

  • Included Audio to exported resource types.

🔧 Changed

  • Improved toolkit UI.

1.0.0-beta.13 - 2024-04-08

🧰 Added

  • Added support for audio resource.
  • Added two optional config, resourcesPerPage and containersPerPage.

🐞 Fixed

  • Fixed type cannot be resolved correctly as ESM modules.
  • Fixed inconsistent width of the scrollable area when user has different OS or browser settings for scrollbar.

1.0.0-beta.12 - 2024-03-26

Fixed

  • Fixed uploading of asset will fail if the asset id contains non-alphanumeric characters.

1.0.0-beta.11 - 2024-03-21

🧰 Added

  • Add support for dynamically fetch filters during run time.
  • Allow Image, Video and Embed resources to have attachments, allow user to add image/video/embed attachments to their design, and open any other attachment types in a new tab.

🔧 Changed

  • Show export button when user has navigated inside a container or search result page.

1.0.0-beta.9 - 2024-03-11

🔨 Breaking changes

  • Change Tag type to be value-label instead of id-name to be consistent with all other filters
  • Remove getFilterTags callback from props (we have a more powerful alternative coming out soon)

🐞 Fixed

  • Fixed clearing filter does not exit search result page when query string is empty.
  • Make scroll smooth when hiding sort bar.

1.0.0-beta.8 - 2024-03-07

🐞 Fixed

  • Fix SearchableListView overflow caused by paddings.
  • Fix progress bar starting too early for export.
  • Add empty message when there are no containers to choose for export.

1.0.0-beta.7 - 2024-03-06

🐞 Fixed

  • Make sure placeholders have consistent shape and layout as assets.
  • Remove border from video cards.
  • Fix export page broken because ToastProvider is unavailable.
  • Fix sort menu blinks when repeatedly triggering scroll event.

1.0.0-beta.5 - 2024-03-05

🔧 Changed

  • UI improvement: Improve masonry display and infinite scrolling using react-infinite-scrollers and Masonry component from @canva/app-ui-kit.
  • UI improvement: Combine error or empty message into one on the "All" tab when no results are available.

1.0.0-beta.4 - 2024-03-01

🐞 Fixed

  • Fix updateDate and createDate cannot be shown on item details view, when provided a date string instead of Date object
  • Fix filter button dis-positioned for some users

1.0.0-beta.3 - 2024-02-28

🔨 Breaking changes

  • Rename config.search.filterFormConfig.containerTypeFilters to config.search.filterFormConfig.containerTypes

🔧 Added

  • Apply generic type ContainerTypeKey on config.search.filterFormConfig.containerTypes, config.export.containerTypes, findResourcesRequest.containerTypes and findResourcesRequest.parentContainerType

1.0.0-beta.2 - 2024-02-27

🔨 Breaking changes

  • Renamed filter fieldName to label

🧰 Added

  • Added additional ListingSurface option for config.containerTypes, "SEARCH". Once included in listingSurfaces of a container type, this container type can be listed in the search results when user is searching using query/filters.

  • Added additional parentContainerType field in FindResourcesRequest, this field be provided in the request when searching for results from inside a container.

🐞 Fixed

  • Fixed search suggestion will pop out unexpectedly when user types in filter form

  • Fixed redundant retry attempts when FindResourcesCallback returns an error

1.0.0-beta.1 - 2024-02-22

🔨 Breaking changes

  • Replaced all exported enums with type unions of string literals.
  • Removed unnecessarily exported types.

🐞 Fixed

  • Fixed importing the package from a node.js environment not working by removing React component from node.js environment export.

Canva Developer Documentation SOP Site