npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

contentful-roku

v1.2.1

Published

BrightScript Contentful SDK

Readme

Contentful Roku

BrightScript Contentful SDK — access the Contentful Content Delivery API from your Roku app.

Installation

npm i contentful-roku

Quick 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 sub

Configuration

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 total or skip. Use pages.next / pages.prev for 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 10

Ordering

{ order: "-sys.createdAt" }         ' newest first
{ order: "fields.title" }           ' alphabetical ascending

Field Selection

{ select: "fields.title,fields.slug,sys.id" }
' sys.id and sys.type are always added automatically

Content 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,id3

Link 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 3

Cursor 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.123

Dependencies

This package requires @dazn/kopytko-utils.


License

MIT License — see LICENSE file for details.