contentful-roku
v1.2.1
Published
BrightScript Contentful SDK
Maintainers
Readme
Contentful Roku
BrightScript Contentful SDK — access the Contentful Content Delivery API from your Roku app.
Installation
npm i contentful-rokuQuick Start
' @import /components/libs/ContentfulSDK.brs from contentful-roku
sub init()
m._contentful = ContentfulSDK({
accessToken: "<My_Contentful_Access_Token>",
space: "<My_Contentful_Space_ID>",
})
m._contentful.fetchEntries({ content_type: "blogPost", limit: 10 }).then(onEntriesReceived)
end sub
sub onEntriesReceived(response as Object)
for each entry in response.items
print entry.fields.title
end for
end subConfiguration
sdk = ContentfulSDK(config, options)Config
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| accessToken | String | Yes | — | Content Delivery (or Preview) API access token |
| space | String | Yes | — | Contentful space ID |
| environment | String | No | "master" | Environment ID |
| baseUrl | String | No | "https://cdn.contentful.com" | Override the API base URL |
| previewMode | Boolean | No | false | Use preview.contentful.com (see Preview API) |
| application | String | No | — | App identifier appended to the User-Agent header |
| integration | String | No | — | Integration identifier appended to the User-Agent header |
| headers | Object | No | {} | Additional headers sent with every request |
| retryOnError | Boolean | No | false | Retry on 429 / 5xx responses |
| retryLimit | Integer | No | 5 | Maximum retry attempts (exponential backoff: 1 s, 2 s, 4 s…) |
Options
| Field | Type | Default | Description |
|---|---|---|---|
| withAllLocales | Boolean | false | Add locale=* to all entry and asset requests |
| withoutLinkResolution | Boolean | false | Skip link resolution; linked fields stay as raw Link objects |
| withoutUnresolvableLinks | Boolean | false | Remove linked fields that cannot be resolved instead of leaving them as Link objects |
| withLocaleBasedPublishing | Boolean | false | Send X-Contentful-Locale-Based-Publishing: true on every request |
sdk = ContentfulSDK({
accessToken: "<token>",
space: "<space_id>",
retryOnError: true,
}, {
withAllLocales: true,
withoutUnresolvableLinks: true,
})API Reference
All methods return a Promise. Use .then(successCallback, errorCallback) to handle results.
fetchSpace()
Fetch space metadata.
m._contentful.fetchSpace().then(sub(space as Object)
print space.name
end sub)Response: { sys: { id, type: "Space" }, name, locales: [...] }
fetchEntry(id, queryParams)
Fetch a single entry by ID. Returns a collection wrapper with one item so links are resolved against any includes the API returns.
m._contentful.fetchEntry("entryId").then(sub(response as Object)
if response.items.count() > 0
entry = response.items[0]
print entry.fields.title
end if
end sub)Response: { items: [Entry], total, skip, limit, includes: { Entry: [...], Asset: [...] } }
fetchEntries(queryParams)
Fetch multiple entries.
m._contentful.fetchEntries({
content_type: "blogPost",
order: "-sys.createdAt",
limit: 10,
include: 2,
}).then(sub(response as Object)
for each entry in response.items
print entry.fields.title
end for
end sub)Response: { items: [Entry], total, skip, limit, includes: { Entry: [...], Asset: [...] } }
fetchEntriesWithCursor(queryParams)
Fetch entries using cursor-based pagination. Use pages.next / pages.prev token values to navigate instead of skip.
m._contentful.fetchEntriesWithCursor({ content_type: "blogPost", limit: 20 }).then(sub(firstPage as Object)
for each entry in firstPage.items
print entry.fields.title
end for
if firstPage.pages.next <> Invalid
m._contentful.fetchEntriesWithCursor({ pageNext: firstPage.pages.next, limit: 20 }).then(onNextPage)
end if
end sub)Response: { items: [Entry], limit, pages: { next?: string, prev?: string }, includes: { Entry: [...], Asset: [...] } }
Cursor pagination does not return
totalorskip. Usepages.next/pages.prevfor navigation.
fetchAsset(id, queryParams)
Fetch a single asset by ID.
m._contentful.fetchAsset("assetId").then(sub(asset as Object)
print asset.fields.file.url
print asset.fields.title
end sub)Response: { sys: { id, type: "Asset", ... }, fields: { title, description, file: { url, details, fileName, contentType } }, metadata }
fetchAssets(queryParams)
Fetch multiple assets.
m._contentful.fetchAssets({ limit: 20 }).then(sub(response as Object)
for each asset in response.items
print asset.fields.title
end for
end sub)Response: { items: [Asset], total, skip, limit }
fetchAssetsWithCursor(queryParams)
Fetch assets using cursor-based pagination.
m._contentful.fetchAssetsWithCursor({ limit: 50 }).then(sub(page as Object)
for each asset in page.items
print asset.fields.file.url
end for
end sub)Response: { items: [Asset], limit, pages: { next?: string, prev?: string } }
createAssetKey(expiresAt)
Create a signed key for accessing embargoed assets. expiresAt is a Unix timestamp (LongInteger).
expiresAt& = CreateObject("roDateTime").asSeconds() + 3600 ' 1 hour from now
m._contentful.createAssetKey(expiresAt&).then(sub(key as Object)
print key.policy
print key.secret
end sub)Response: { policy: string, secret: string }
fetchContentType(id)
Fetch a single content type definition.
m._contentful.fetchContentType("blogPost").then(sub(contentType as Object)
print contentType.name
for each field in contentType.fields
print field.id + ": " + field.type
end for
end sub)Response: { sys, name, description, displayField, fields: [{ id, name, type, localized, required, ... }] }
fetchContentTypes(queryParams)
Fetch all content types in the environment.
m._contentful.fetchContentTypes().then(sub(response as Object)
for each contentType in response.items
print contentType.name
end for
end sub)Response: { items: [ContentType], total, skip, limit }
fetchLocales(queryParams)
Fetch all locales configured for the environment.
m._contentful.fetchLocales().then(sub(response as Object)
for each locale in response.items
print locale.name + " (" + locale.code + ")"
if locale.default then print " ^ default locale"
end for
end sub)Response: { items: [{ sys, code, name, default, fallbackCode }], total, skip, limit }
fetchTag(id)
Fetch a single tag.
m._contentful.fetchTag("tagId").then(sub(tag as Object)
print tag.name
end sub)Response: { sys: { id, version, visibility, ... }, name }
fetchTags(queryParams)
Fetch all tags.
m._contentful.fetchTags().then(sub(response as Object)
for each tag in response.items
print tag.name
end for
end sub)Response: { items: [Tag], total, skip, limit }
fetchConcept(id, queryParams)
Fetch a single taxonomy concept.
m._contentful.fetchConcept("conceptId").then(sub(concept as Object)
print concept.sys.id
print concept.prefLabel["en-US"]
end sub)Response: { sys: { id, type: "TaxonomyConcept", version, ... }, prefLabel, altLabels, hiddenLabels, definition, broader, related, conceptSchemes, ... }
fetchConcepts(queryParams)
Fetch all taxonomy concepts. Uses cursor-based pagination.
m._contentful.fetchConcepts({ limit: 100 }).then(sub(response as Object)
for each concept in response.items
print concept.prefLabel["en-US"]
end for
end sub)Response: { items: [Concept], limit, pages: { next?: string, prev?: string } }
fetchConceptScheme(id)
Fetch a single taxonomy concept scheme.
m._contentful.fetchConceptScheme("schemeId").then(sub(scheme as Object)
print scheme.prefLabel["en-US"]
print scheme.totalConcepts
end sub)Response: { sys: { id, type: "TaxonomyConceptScheme", ... }, prefLabel, definition, topConcepts: [...], concepts: [...], totalConcepts }
fetchConceptSchemes(queryParams)
Fetch all taxonomy concept schemes. Uses cursor-based pagination.
m._contentful.fetchConceptSchemes().then(sub(response as Object)
for each scheme in response.items
print scheme.prefLabel["en-US"]
end for
end sub)Response: { items: [ConceptScheme], limit, pages: { next?: string, prev?: string } }
fetchConceptAncestors(id, queryParams)
Fetch ancestor concepts of a given concept in the taxonomy hierarchy.
m._contentful.fetchConceptAncestors("conceptId").then(sub(response as Object)
for each ancestor in response.items
print ancestor.prefLabel["en-US"]
end for
end sub)Response: { items: [Concept], limit, pages: { next?: string, prev?: string } }
fetchConceptDescendants(id, queryParams)
Fetch descendant concepts of a given concept in the taxonomy hierarchy.
m._contentful.fetchConceptDescendants("conceptId").then(sub(response as Object)
for each descendant in response.items
print descendant.prefLabel["en-US"]
end for
end sub)Response: { items: [Concept], limit, pages: { next?: string, prev?: string } }
parseEntries(data)
Resolve links in a raw entry collection you already have (e.g. cached data fetched with withoutLinkResolution). Respects the withoutUnresolvableLinks constructor option.
' Fetch raw data without resolving links
sdk = ContentfulSDK(config, { withoutLinkResolution: true })
sdk.fetchEntries({ content_type: "blogPost" }).then(sub(rawData as Object)
' Resolve links later (e.g. after enriching includes from another source)
resolved = m._contentful.parseEntries(rawData)
for each entry in resolved.items
print entry.fields.author.fields.name ' now resolved
end for
end sub)Query Parameters
Query parameters are passed as an associative array to any fetch* method that accepts queryParams.
Pagination
{ skip: 20, limit: 10 } ' offset pagination: page 3 of 10Ordering
{ order: "-sys.createdAt" } ' newest first
{ order: "fields.title" } ' alphabetical ascendingField Selection
{ select: "fields.title,fields.slug,sys.id" }
' sys.id and sys.type are always added automaticallyContent Type Filter
{ content_type: "blogPost" }Include Depth (link resolution levels)
{ include: 2 } ' resolve up to 2 levels of linked content (0–10)Filtering Operators
| Operator | Example | Meaning |
|---|---|---|
| (none) | "fields.category": "tech" | Exact match |
| [ne] | "fields.status[ne]": "draft" | Not equal |
| [exists] | "fields.image[exists]": "true" | Field exists |
| [in] | "sys.id[in]": ["id1", "id2"] | Match any value (arrays are joined automatically) |
| [nin] | "fields.tag[nin]": "archived" | Exclude values |
| [all] | "fields.tags[all]": "featured" | All values match |
| [gte] | "fields.price[gte]": "10" | Greater than or equal |
| [lte] | "fields.price[lte]": "100" | Less than or equal |
| [gt] | "sys.createdAt[gt]": "2024-01-01" | Greater than |
| [lt] | "sys.updatedAt[lt]": "2025-01-01" | Less than |
| [match] | "fields.body[match]": "roku" | Full-text search |
| [near] | "fields.location[near]": "37.8,-122.4,10" | Proximity (lat,lon,km) |
| [within] | "fields.location[within]": "38,-123,37,-122" | Bounding box |
Metadata Filters (Tags & Concepts)
{ "metadata.tags.sys.id[in]": ["featured", "news"] }
{ "metadata.concepts.sys.id[in]": "conceptId" }Array Parameter Handling
Arrays are automatically converted to comma-separated strings:
{ "sys.id[in]": ["id1", "id2", "id3"] }
' Sent as: sys.id[in]=id1,id2,id3Link Resolution
By default the SDK resolves linked entries and assets in the response — linked fields are replaced with the full object from includes.
Disable link resolution
When withoutLinkResolution: true, linked fields are returned as raw Link objects:
sdk = ContentfulSDK(config, { withoutLinkResolution: true })
sdk.fetchEntries({ content_type: "article" }).then(sub(response as Object)
entry = response.items[0]
' entry.fields.author is { sys: { type: "Link", linkType: "Entry", id: "..." } }
print entry.fields.author.sys.id
end sub)Remove unresolvable links
When withoutUnresolvableLinks: true, linked fields that cannot be resolved (e.g. unpublished or deleted content) are removed from the response entirely rather than left as Link objects:
sdk = ContentfulSDK(config, { withoutUnresolvableLinks: true })Cursor vs Offset Pagination
The SDK supports two pagination styles:
Offset pagination (fetchEntries, fetchAssets) — use skip and limit. Suited for jumping to a specific page.
{ skip: 40, limit: 20 } ' page 3Cursor pagination (fetchEntriesWithCursor, fetchAssetsWithCursor, all taxonomy methods) — use the pages.next / pages.prev tokens. More efficient for sequential traversal; does not expose total.
' First page
sdk.fetchEntriesWithCursor({ content_type: "product", limit: 50 }).then(sub(page as Object)
' process page.items...
if page.pages.next <> Invalid
sdk.fetchEntriesWithCursor({ pageNext: page.pages.next, limit: 50 }).then(onNextPage)
end if
end sub)Preview API
To access draft (unpublished) content, use the Preview API:
sdk = ContentfulSDK({
accessToken: "<My_Preview_Access_Token>",
space: "<My_Space_ID>",
previewMode: true,
})This automatically switches the base URL to https://preview.contentful.com. You can also set baseUrl directly if you need a custom endpoint.
Error Handling
All methods return promises that resolve on success or reject on failure. The rejection value contains statusCode and error.
m._contentful.fetchEntries({ content_type: "blogPost" }).then(sub(response as Object)
print "Received " + response.total.toStr() + " entries"
end sub, sub(error as Object)
print "HTTP " + error.statusCode.toStr() + ": " + error.error.toStr()
end sub)User-Agent
The SDK automatically sets a X-Contentful-User-Agent header:
sdk contentful.roku; platform roku; os <roku_version>Customise the application and integration parts via config:
ContentfulSDK({
accessToken: "...",
space: "...",
application: "MyApp/1.0",
integration: "MyIntegration/2.1",
})
' X-Contentful-User-Agent: MyApp/1.0; MyIntegration/2.1; sdk contentful.roku; platform roku; os 13.0.0.123Dependencies
This package requires @dazn/kopytko-utils.
License
MIT License — see LICENSE file for details.
