Appearance
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.
- The backend retrieves images from Lorem Picsum, a service that provides placeholder photos.
- The frontend uses the SearchableListView component to present the resources to users.
- To retrieve the next page of resources, the backend uses the
continuationproperty.
Step 1: Configure the service
In your backend code, create a basic Node.js server (using Express), and import modules for handling the Canva app components and HTTP requests:
typescriptimport { 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.
To retrieve a list of images from Lorem Picsum, send a
GETrequest to this URL:bashThe 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/..." }To retrieve a different page of images, append a
pageparameter to the URL:bashTo perform the request, call the
requestmethod:typescriptconst options = { url: "https://picsum.photos/v2/list", params: { page: currentPage, }, }; const picsum = await axios.request(options);Convert the data into a format that Canva can process:
typescriptconst 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:
- Parses the number from the
currentPagevariable. - Increments the page number by one.
- 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:
Enable exports in the
SearchableListViewconfig.tsfile:ts{ export: { enabled: true, estimatedUploadTimeMs: 1500, acceptedFileTypes: ["gif", "jpg"] } }The available options for the
exportproperty include:enabled: Set astrueto enable exports.acceptedFileTypes: Include a list of file types that can be exported.requireContainerSelected: Set astrueif 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.
Add a
saveExportedDesignprop 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
OauthAccountSwitchersoonAccountSwitchreceivesnullwhen there is no active account (after the last account was disconnected).
2.3.0 - 2026-04-02
🧰 Added
- Added
AccountSwitcher(controlled) andOauthAccountSwitcher(self-contained) composite components for multi-account Oauth flows.AccountSwitcheraccepts accounts and callbacks as props for full developer control.OauthAccountSwitcherwraps it and accepts aMultiAccountOauthclient from@canva/user, managing account loading, switching, adding, and disconnection internally. Added@canva/useras 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-kitto version5.1.0, see@canva/app-ui-kitchangelogs here. - Upgraded
reactfrom^19.0.0to^19.2.0 - Upgraded
react-domfrom ``^19.0.0to^19.2.0`
2.0.0 - 2025-10-01
🔧 Changed
- Upgraded
@canva/app-ui-kitto version 5, see@canva/app-ui-kitchangelogs here. - Upgraded
reactfrom^18.3.1to^19.0.0 - Upgraded
react-domfrom ``^18.3.1to^19.0.0` - Upgraded
react-intlfrom6.6.8to7.1.11 - Upgraded peer dependencies for use with
reactv19
1.3.1 - 2025-07-24
🔧 Changed
- Upgraded
@canva/designfrom version ^2.4.1 to ^2.7.0. - Upgraded
@canva/platformfrom version ^2.1.0 to ^2.2.0.
1.3.0 - 2025-04-22
🧰 Added
Added
thumbnailCorsCheckoption toConfig.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.combefore 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,SearchableListViewalways uses best effort to check if the thumbnail is CORS enabled by sending a pre-flight request for the thumbnail withcrossOrigin: "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-<app-id>.canva-apps.com), so it is not an accurate check if the thumbnail only enables CORS forwww.canva.com. If your thumbnail only allowswww.canva.comas a CORS origin, you can disable the CORS check by settingthumbnailCorsCheckto{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/assetfrom 2.0.0 to 2.2.0 - Upgraded
@canva/designfrom 2.2.1 to 2.4.1 - Upgraded
@canva/errorfrom 2.0.0 to 2.1.0 - Upgraded
@canva/platformfrom 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-kitto version 4.8.0, see@canva/app-ui-kitchangelogs here. - Upgraded
reactfrom^18.1.0to^18.3.1 - Upgraded
react-domfrom^18.1.0to^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
fileSizeKBfield forImageVideoandAudiotypes 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
ImageorVideodo 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
Thumbnailtype that's used forthumbnailfield ofImage,Video,Embed,AudioandContainer.
🔧 Changed
- Remove dependency on
@radix-ui/react-tabs, use Tabs component from@canva/app-ui-kitinstead. - Hide tabs that contain no results.
🐞 Fixed
- Fixed not continuing to call
findResourcesCallbackif the previous page has a non-emptycontinuationbut 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/assetsfor Lottie files and DAM template does not support Lottie files yet. - Removed
visibilityfield fromContainer.
🧰 Added
- Added
accessfield toContainer, 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
saveExportedDesigncallback to include an optionalerrorMessagefield in the returned object. If anderrorMessageis provided when export failed, the message will be displayed in the alert message.
1.0.0-beta.29 - 2024-10-03
🧰 Added
- Added
export.requireContainerSelectedfield (defaulttrue) forConfigto control whether user has to select a container before exporting their design.
🔧 Changed
- Use
@canva/app-ui-kit componentsDateInput and Flyout in the filter form. - Use
ImageMimeTypeVideoMimeTypeandAudioMimeTypefrom@canva/assetpackage, 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/designfrom 2.0.0 to 2.1.0.
1.0.0-beta.28 - 2024-09-24
🔨 Breaking changes
acceptedFileTypesforconfig.exporthas changed fromArray<"PNG" | "JPG" | "PDF_STANDARD" | "VIDEO" | "GIF" | "PPTX" | "SVG">toArray<"png" | "jpg" | "pdf_standard" | "video" | "gif" | "pptx" | "svg">to adapt to@canva/assetv2ExportFileTypetype.
🔧 Changed
- Upgraded
@canva/asset,@canva/design@canva/platformandcanva/errorto version 2.0.0. - Updated
@canva/app-ui-kitversion to 4.0.0 (changelog)
1.0.0-beta.26 - 2024-09-19
🧰 Added
The
thumbnailof 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,AudioandVideolisted inFindResourcesResponseto not provide aurl. When the resource does not have aurl, it has to provide agetUrl: () => Promise<string>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
durationMsforAudiois0or 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?: stringtosaveExportedDesign. ThedesignTitlewill 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
enableAppStatePersistenceis 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
dragAndDropPreviewtoImage,VideoandEmbedresource 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 atthumbnail.urldoes not allowcanva.comas 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
tabfield inFindResourcesRequestto 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
tabsfield forContainerType, 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
tabValuesfield forOVERVIEWtab 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
Audioto 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,
resourcesPerPageandcontainersPerPage.
🐞 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,VideoandEmbedresources to haveattachments, 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
Tagtype to be value-label instead of id-name to be consistent with all other filters - Remove
getFilterTagscallback 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-scrollersandMasonrycomponent 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
updateDateandcreateDatecannot be shown on item details view, when provided a datestringinstead ofDateobject - Fix filter button dis-positioned for some users
1.0.0-beta.3 - 2024-02-28
🔨 Breaking changes
- Rename
config.search.filterFormConfig.containerTypeFilterstoconfig.search.filterFormConfig.containerTypes
🔧 Added
- Apply generic type ContainerTypeKey on
config.search.filterFormConfig.containerTypes,config.export.containerTypes,findResourcesRequest.containerTypesandfindResourcesRequest.parentContainerType
1.0.0-beta.2 - 2024-02-27
🔨 Breaking changes
- Renamed filter
fieldNametolabel
🧰 Added
Added additional
ListingSurfaceoption 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
parentContainerTypefield inFindResourcesRequest, 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
FindResourcesCallbackreturns 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.