Use Cloudinary with Prismic
Learn how to build websites with Cloudinary and Prismic.
This guide shows how to integrate Cloudinary with Prismic to deliver optimized images and videos in your content. You’ll learn how to create custom integration catalogs that connect your Cloudinary media library to Prismic’s integration fields, giving developers full control over media delivery and optimization.
You’ll learn two approaches:
- Creating an images catalog to select and display optimized images.
- Creating a videos catalog to select and stream video content.
Both methods allow content writers to choose Cloudinary media directly from the Prismic editor while giving developers control over transformations, optimizations, and delivery.
Prerequisites
Before you begin, you’ll need to set up Cloudinary API access.
Get your Cloudinary API credentials
Navigate to your Cloudinary Dashboard to find your account credentials. You’ll need these values for API access:
Credential Description Cloud Name Your unique Cloudinary subdomain API Key Public key for authenticating API requests API Secret Private key for secure API operations (keep this secret) Learn more about Cloudinary API credentials
Set up environment variables
After finding your credentials, save them as environment variables in a
.env
file:.env# Your Cloudinary cloud name NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=your_cloud_name # Your Cloudinary API key CLOUDINARY_API_KEY=your_api_key # Your Cloudinary API secret (keep this secure!) CLOUDINARY_API_SECRET=your_api_secret
Display Cloudinary images
Follow these steps when content writers need to select and display images from your Cloudinary media library.
Add an integration endpoint for images
Making images available to integration fields requires a custom integration catalog.
To begin, install Cloudinary’s Node.js SDK.
npm install cloudinary
Next, add an API endpoint that returns your Cloudinary images.
src/app/api/images/route.tsimport type { IntegrationAPIItem, IntegrationAPIResults, } from "@prismicio/client"; import { v2 as cloudinary, type ResourceApiResponse } from "cloudinary"; export const dynamic = "force-dynamic"; const MAX_PER_PAGE = 50; cloudinary.config({ cloud_name: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME, api_key: process.env.CLOUDINARY_API_KEY, api_secret: process.env.CLOUDINARY_API_SECRET, }); export type CloudinaryImage = { public_id: string; url: string; width: number; height: number; alt?: string; }; export async function GET(request: Request) { const { searchParams } = new URL(request.url); const page = parseInt(searchParams.get("page") ?? "1") || 1; // Fetch images from Cloudinary const result: ResourceApiResponse = await cloudinary.api.resources({ type: "upload", resource_type: "image", max_results: 500, prefix: "", // Optional: filter by folder context: true, // Include context metadata for alt text }); const images = result.resources || []; // Paginate results const startIndex = (page - 1) * MAX_PER_PAGE; const endIndex = startIndex + MAX_PER_PAGE; const paginatedImages = images.slice(startIndex, endIndex); const results: IntegrationAPIItem<CloudinaryImage>[] = paginatedImages.map( (image) => ({ id: image.public_id, title: image.public_id.split("/").pop()?.replace(/[-_]/g, " ") || image.public_id, description: `Dimensions: ${image.width}x${image.height} | Format: ${image.format}`, image_url: image.secure_url, last_update: new Date(image.created_at).getTime(), blob: { public_id: image.public_id, url: image.secure_url, width: image.width, height: image.height, alt: (image.context as { custom?: { alt?: string } })?.custom?.alt, }, }), ); const response: IntegrationAPIResults<CloudinaryImage> = { results_size: images.length, results, }; return Response.json(response); }
Deploy your website
Your API endpoint needs to be deployed and accessible by Prismic.
Follow the deployment instructions for Next.js, Nuxt, or SvelteKit before continuing. Remember to add your environment variables to your deployment.
You’ll use your website’s deployed URL in the next step.
Create an images integration catalog
Follow the linked guide to connect the API endpoint to a custom integration catalog.
Learn how to create a custom integration catalog
Use the following field values when creating the catalog:
Field Description Catalog Name ”Cloudinary Images” Description ”Images from Cloudinary media library.” Endpoint The full public URL to the API endpoint (e.g. https://example.com/api/images
)Access Token An optional secret string used to authenticate API calls. Add an image field to a content model
After creating the catalog, connect it to an integration field in a slice, page type, or custom type depending on where you need the image data.
Learn how to add an integration field
Display the image
After fetching an image from Cloudinary, you need to display it with optimizations. The following examples show how to use the official
CldImage
component with automatic optimizations.First, install the official Cloudinary package specific to your framework:
npm install next-cloudinary
Then use it in your website. The following example shows how to use the component in a slice.
src/slices/HeroImage/index.tsx"use client"; import type { Content } from "@prismicio/client"; import { isFilled } from "@prismicio/client"; import type { SliceComponentProps } from "@prismicio/react"; import { CldImage } from "next-cloudinary"; import type { CloudinaryImage } from "@/app/api/images/route"; type HeroImageProps = SliceComponentProps<Content.HeroImageSlice>; export default function HeroImage({ slice }: HeroImageProps) { if (!isFilled.integration(slice.primary.image)) { return null; } const image = slice.primary.image as CloudinaryImage; return ( <section> <CldImage src={image.public_id} alt={image.alt} width={image.width} height={image.height} /> </section> ); }
Learn more about the CldImage component
Add an image to a page
Test your new field by selecting an image in a page. If everything was set up correctly, you should see the optimized image displayed on your page with automatic transformations applied.
Image metadata
The image data returned by Prismic’s Content API includes the following fields:
public_id
string
The image’s unique identifier in Cloudinary.
url
string
width
number
height
number
alt
string | undefined
Here is an example of what an integration field containing a Cloudinary image looks like from the Content API:
{
"public_id": "samples/landscapes/beach-boat",
"url": "https://res.cloudinary.com/demo/image/upload/v1234567890/samples/landscapes/beach-boat.jpg",
"width": 2560,
"height": 1707,
"alt": "A small fishing boat anchored near a tropical beach with crystal clear turquoise water"
}
Display Cloudinary videos
Follow these steps when content writers need to select and stream videos from your Cloudinary media library.
Add an integration endpoint for videos
Making videos available to integration fields requires a separate custom integration catalog.
To begin, install Cloudinary’s Node.js SDK.
npm install cloudinary
Next, add an API endpoint that returns your Cloudinary videos.
src/app/api/videos/route.tsimport type { IntegrationAPIItem, IntegrationAPIResults, } from "@prismicio/client"; import { v2 as cloudinary, type ResourceApiResponse } from "cloudinary"; export const dynamic = "force-dynamic"; const MAX_PER_PAGE = 50; cloudinary.config({ cloud_name: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME, api_key: process.env.CLOUDINARY_API_KEY, api_secret: process.env.CLOUDINARY_API_SECRET, }); export type CloudinaryVideo = { public_id: string; url: string; width: number; height: number; }; export async function GET(request: Request) { const { searchParams } = new URL(request.url); const page = parseInt(searchParams.get("page") ?? "1") || 1; // Fetch videos from Cloudinary const result: ResourceApiResponse = await cloudinary.api.resources({ type: "upload", resource_type: "video", max_results: 500, prefix: "", // Optional: filter by folder }); const videos = result.resources || []; // Paginate results const startIndex = (page - 1) * MAX_PER_PAGE; const endIndex = startIndex + MAX_PER_PAGE; const paginatedVideos = videos.slice(startIndex, endIndex); const results: IntegrationAPIItem<CloudinaryVideo>[] = paginatedVideos.map( (video) => ({ id: video.public_id, title: video.public_id.split("/").pop()?.replace(/[-_]/g, " ") || video.public_id, description: `Duration: ${video.duration ? Math.round(video.duration) + "s" : "N/A"} | Format: ${video.format} | Size: ${(video.bytes / 1024 / 1024).toFixed(2)}MB`, image_url: video.secure_url.replace(/\.\w+$/, ".jpg"), // Generate thumbnail last_update: new Date(video.created_at).getTime(), blob: { public_id: video.public_id, url: video.secure_url, width: video.width, height: video.height, }, }), ); const response: IntegrationAPIResults<CloudinaryVideo> = { results_size: videos.length, results, }; return Response.json(response); }
Deploy your website
Your API endpoint needs to be deployed and accessible by Prismic.
Follow the deployment instructions for Next.js, Nuxt, or SvelteKit before continuing. Remember to add your environment variables to your deployment.
You’ll use your website’s deployed URL in the next step.
Create a videos integration catalog
Follow the linked guide to connect the API endpoint to a custom integration catalog.
Learn how to create a custom integration catalog
Use the following field values when creating the catalog:
Field Description Catalog Name ”Cloudinary Videos” Description ”Videos from Cloudinary media library.” Endpoint The full public URL to the API endpoint (e.g. https://example.com/api/videos
)Access Token An optional secret string used to authenticate API calls. Add a video field to a content model
After creating the catalog, connect it to an integration field in a slice, page type, or custom type depending on where you need the video data.
Learn how to add an integration field
Display the video
After fetching a video from Cloudinary, you need to display it with optimizations. The following examples show how to use the official
CldVideoPlayer
component with automatic optimizations and adaptive streaming.First, install the official Cloudinary package specific to your framework:
npm install next-cloudinary
Then use it in your website. The following example shows how to use the component in a slice.
src/slices/VideoPlayer/index.tsx"use client"; import type { Content } from "@prismicio/client"; import { isFilled } from "@prismicio/client"; import type { SliceComponentProps } from "@prismicio/react"; import { CldVideoPlayer } from "next-cloudinary"; import type { CloudinaryVideo } from "@/app/api/videos/route"; import "next-cloudinary/dist/cld-video-player.css"; type VideoPlayerProps = SliceComponentProps<Content.VideoPlayerSlice>; export default function VideoPlayer({ slice }: VideoPlayerProps) { if (!isFilled.integration(slice.primary.video)) { return null; } const video = slice.primary.video as CloudinaryVideo; return ( <section> <CldVideoPlayer src={video.public_id} width={video.width} height={video.height} /> </section> ); }
Learn more about the CldVideoPlayer component
Add a video to a page
Test your new field by selecting a video in a page. If everything was set up correctly, you should see the video player with adaptive streaming and optimizations applied.
Video metadata
The video data returned by Prismic’s Content API includes the following fields:
public_id
string
The video’s unique identifier in Cloudinary
url
string
width
number
height
number
Here is an example of what an integration field containing a Cloudinary video looks like from the Content API:
{
"public_id": "samples/videos/sea_turtle",
"url": "https://res.cloudinary.com/demo/video/upload/v1234567890/samples/videos/sea_turtle.mp4",
"width": 1920,
"height": 1080
}