Connect CE.SDK to the Pexels API to search and add royalty-free stock photos directly to your designs.
Pexels offers a large library of high-quality, royalty-free stock photos through a public REST API. You expose that library inside CE.SDK by implementing a custom asset source: a small class that conforms to the AssetSource protocol, fetches results from the Pexels API, and maps them into the asset format the engine understands. Once registered, the source behaves like any other — it can be queried programmatically and surfaced in the asset library.
Prerequisites#
- A Pexels API key from the Pexels API documentation.
- Familiarity with Pexels’ API guidelines and rate limits.
No third-party dependencies are required — the example uses URLSession and Codable from Foundation.
Register the Asset Source#
A custom asset source is any object conforming to the AssetSource protocol. Register it with engine.asset.addSource(_:), passing your Pexels API key so the source can authenticate its requests:
// Replace with your Pexels API key from https://www.pexels.com/api/let apiKey = "YOUR_PEXELS_API_KEY"let source = PexelsAssetSource(apiKey: apiKey)try engine.asset.addSource(source)Pass your own key in place of the YOUR_PEXELS_API_KEY placeholder. Pexels expects the key in the Authorization header of every request, which the source sets for you.
After registering, the source ID appears in engine.asset.findAllSources() alongside the engine’s built-in sources:
let registeredSources = engine.asset.findAllSources()print("Registered asset sources: \(registeredSources)")Implement findAssets#
The source wraps two Pexels endpoints — /v1/search for queries and /v1/curated for browsing without a search term. CE.SDK uses 0-based page indices, so it converts each page to the 1-based index Pexels expects:
public final class PexelsAssetSource: NSObject { private let decoder = JSONDecoder() private let apiKey: String
public init(apiKey: String) { self.apiKey = apiKey }
private struct Endpoint { let path: String let query: [URLQueryItem]
static func search(queryData: AssetQueryData) -> Self { Endpoint( path: "/v1/search", query: [ .init(name: "query", value: queryData.query), .init(name: "page", value: String(queryData.page + 1)), .init(name: "per_page", value: String(queryData.perPage)), ], ) }
static func curated(queryData: AssetQueryData) -> Self { Endpoint( path: "/v1/curated", query: [ .init(name: "page", value: String(queryData.page + 1)), .init(name: "per_page", value: String(queryData.perPage)), ], ) }
var url: URL? { var components = URLComponents() components.scheme = "https" components.host = "api.pexels.com" components.path = path components.queryItems = query return components.url } }}Conform to AssetSource by giving the source a unique id and implementing findAssets(queryData:) async throws -> AssetQueryResult. The method receives the current query — a search string plus pagination — and returns a page of results. Because it is async, you can back it with URLSession, a cache, or any third-party client:
public static let id = "pexels"
public var id: String { Self.id}
public func findAssets(queryData: AssetQueryData) async throws -> AssetQueryResult { let endpoint: Endpoint = queryData.query? .isEmpty ?? true ? .curated(queryData: queryData) : .search(queryData: queryData)
var request = URLRequest(url: endpoint.url!) request.setValue(apiKey, forHTTPHeaderField: "Authorization") let data = try await URLSession.shared.data(for: request).0
let response = try decoder.decode(PexelsSearchResponse.self, from: data) let hasNextPage = response.nextPage != nil && !response.photos.isEmpty
return .init( assets: response.photos.map(AssetResult.init), currentPage: queryData.page, nextPage: hasNextPage ? queryData.page + 1 : -1, total: response.totalResults ?? -1, )}findAssets routes to /v1/search when the query string is non-empty and to /v1/curated otherwise, sends the request with the API key in the Authorization header, then decodes the response into an AssetQueryResult:
currentPageechoes the requested page.nextPageis the next page to request, or-1when no more results exist — Pexels returns anext_pageURL only while there are more.totalis the overall result count; the curated endpoint omits it, so the source reports-1to signal an unknown total.
Translate Pexels Photos to Assets#
Each Pexels photo becomes an AssetResult. The meta dictionary carries the values the engine needs to add the image to a scene:
convenience init(photo: PexelsPhoto) { self.init( id: String(photo.id), locale: "en", label: photo.alt, meta: [ "uri": photo.src.original.absoluteString, "thumbUri": photo.src.medium.absoluteString, "blockType": DesignBlockType.graphic.rawValue, "fillType": FillType.image.rawValue, "shapeType": ShapeType.rect.rawValue, "kind": "image", "width": String(photo.width), "height": String(photo.height), ], context: .init(sourceID: PexelsAssetSource.id), credits: .init( name: photo.photographer, url: photo.photographerURL.flatMap(URL.init(string:)), ), utm: .init(source: "CE.SDK Demo", medium: "referral"), )}uriis the full-resolution image (src.original);thumbUriis the preview shown in the asset library (src.medium).blockTypeandfillTypetell the engine to create a graphic block with an image fill when the asset is applied. Setting them up front avoids a round-trip to infer the type from the file.shapeTypeandkindgive the block a rectangle shape and mark its content as an image, so it renders correctly without further setup.widthandheightcome straight from the API so the engine can size the block correctly.context.sourceIDlinks the result back to the Pexels source.
Handle Attribution#
Pexels’ license asks you to credit photographers. Provide attribution at two levels. Source-level credits and license describe the provider and apply to every result:
public var credits: AssetCredits? { .init( name: "Pexels", url: URL(string: "https://www.pexels.com/")!, )}
public var license: AssetLicense? { .init( name: "Pexels license (free)", url: URL(string: "https://www.pexels.com/license/")!, )}Per-asset credits (set in the translation above) name the individual photographer with a link to their Pexels profile, and the utm parameters tag outgoing links for analytics.
Query the Source#
Once registered, query the source through the standard Asset API. Pass an empty query to fetch curated photos, or a search term to search the library — pagination works the same way for both:
let curated = try await engine.asset.findAssets( sourceID: PexelsAssetSource.id, query: .init(query: nil, page: 0, perPage: 20),)print("Pexels returned \(curated.assets.count) curated photos")
let results = try await engine.asset.findAssets( sourceID: PexelsAssetSource.id, query: .init(query: "mountains", page: 0, perPage: 20),)print("Pexels search returned \(results.assets.count) photos for 'mountains'")The returned AssetResult values can be added to a scene with engine.asset.apply(sourceID:assetResult:). Registering the source makes it queryable through the Asset API but does not add it to an editor’s asset library — on iOS, add a library section for it as shown in the Customize Asset Library guide.
Troubleshooting#
- Authentication errors: Confirm the key is sent in the
Authorizationheader and is valid for the Pexels API. - Rate limiting: Pexels enforces per-key rate limits and returns HTTP 429 when exceeded. Cache results and back off on 429 responses.
- Missing attribution: Verify both the source-level
credits/licenseand the per-assetcreditsare populated from the API response. - Images fail to load: Check that the Pexels CDN URLs in
srcare reachable from the device’s network.
API Reference#
Methods#
| Method | Description |
|---|---|
engine.asset.addSource(_:) |
Register an object conforming to AssetSource (such as PexelsAssetSource) as a custom source. |
engine.asset.findAllSources() |
List the IDs of every registered asset source. |
engine.asset.findAssets(sourceID:query:) |
Query a source for a page of results, returning an AssetQueryResult. |
Next Steps#
- Integrate Unsplash Images — Add another remote stock-image source.
- Customize Asset Library — On iOS, surface the source in the editor’s asset panels.
- Insert Shapes or Stickers — Query and apply assets from a registered source.
- Serve Assets From Your Server — Host and register your own asset content.
- Asset Concepts — Learn the core import and asset-source concepts.