# How to embed CSS + JS for the Bitmovin UI with React Native?

**URL:** <https://community.bitmovin.com/t/how-to-embed-css-js-for-the-bitmovin-ui-with-react-native/3817>\
**Category:** Feature Requests\
**Tags:** bitmovin-player, bitmovin, react-native\
**Created:** [October 24, 2025, 9:51am UTC](https://community.bitmovin.com/t/how-to-embed-css-js-for-the-bitmovin-ui-with-react-native/3817 "2025-10-24T09:51:41Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![pierre.poupin](https://sea1.discourse-cdn.com/flex015/user_avatar/community.bitmovin.com/pierre.poupin/32/1065_2.png) [@pierre.poupin](https://community.bitmovin.com/u/pierre.poupin)\
**Post date:** [October 24, 2025, 9:51am UTC](https://community.bitmovin.com/t/how-to-embed-css-js-for-the-bitmovin-ui-with-react-native/3817/1 "2025-10-24T09:51:41Z")

</div>

### I confirm this is a Feature Request and NOT a support/technical query. Please use your Bitmovin Dashboard for raising support tickets.

on

### Which product is your request related to?

Player

### Description

Hey!

First of all, I’m putting this as feature request as it could be part of the React Native example app 😇

As recommended here, we’d like to embed our Bitmovin Player UI with the app bundle directly for offline support.

> **[Using a customized UI](https://developer.bitmovin.com/playback/docs/using-a-custom-ui-build#mobile)**
>
> Once the UI has been customized to suit your requirements, the next step is to prepare it for production and ensure its seamless integration across various platforms. Web Depending on the structure of your build pipeline and workflow, you may...

However, the implementation with React Native + Expo is a bit tricky.  
I’m trying to embed the assets using expo-assets, and reference them through local URIs. It’s a bit tricky especially because you can’t require the `.js` file. So, we rename the file to `.bmjs` extension, whitelist them for expo-assets, and at runtime we rename the file to `.js`. I do get a local URI with a `file://` pointing to a local JS file, embedded in the phone, and yet it does not work with Bitmovin. No UI loads.

I wonder if I have done something wrong, and if there is a better strategy for that.

My post is a bit ambiguous between support / bug / feature request, but as I said above: I think this should be part of the documentation or part of the example.

Any help appreciated 🙌

Thanks!

---

<div class="post-metadata">

**Author:** ![pierre.poupin](https://sea1.discourse-cdn.com/flex015/user_avatar/community.bitmovin.com/pierre.poupin/32/1065_2.png) [@pierre.poupin](https://community.bitmovin.com/u/pierre.poupin)\
**Post date:** [October 24, 2025, 3:00pm UTC](https://community.bitmovin.com/t/how-to-embed-css-js-for-the-bitmovin-ui-with-react-native/3817/2 "2025-10-24T15:00:02Z")

</div>

Dumb mistake on my end, what I’ve done actually works.

If you’re interested, here’s a piece of code that solves it.

```typescript
import { Asset } from 'expo-asset'
import * as FileSystem from 'expo-file-system/legacy'

const asFileWithExt = async (moduleRef: number, desiredExt: 'js' | 'css') => {
  const asset = Asset.fromModule(moduleRef)
  await asset.downloadAsync()

  if (!FileSystem.cacheDirectory || !asset.hash) {
    throw new Error('asdflkjasdknj')
  }

  const dest = `${FileSystem.cacheDirectory}${asset.hash}.${desiredExt}`

  if (asset.localUri) {
    await FileSystem.copyAsync({ from: asset.localUri, to: dest })
  }

  return dest
}

export const getBitmovinUrl = async () => {
  const js = await asFileWithExt(require('./assets/player-ui.bmjs'), 'js')
  const css = await asFileWithExt(require('./assets/player-ui-css.bmcss'), 'css')

  return { js, css }
}

```

---

<div class="post-metadata">

**Author:** ![pierre.poupin](https://sea1.discourse-cdn.com/flex015/user_avatar/community.bitmovin.com/pierre.poupin/32/1065_2.png) [@pierre.poupin](https://community.bitmovin.com/u/pierre.poupin)\
**Post date:** [February 3, 2026, 3:23pm UTC](https://community.bitmovin.com/t/how-to-embed-css-js-for-the-bitmovin-ui-with-react-native/3817/3 "2026-02-03T15:23:12Z")

</div>

Hey!

Actually, the method above caused two major issues:

- the js / css files were not working properly for some reason (strange encoding bugs) on Android
- and it is not working at all on a Distribution app on iOS! (everything is sandboxed)

✅We ended up copying the files during the prebuild phase of expo with a custom expo plugin.

```auto
const { withXcodeProject, withDangerousMod } = require('@expo/config-plugins')
const path = require('path')
const fs = require('fs')

const BITMOVIN_ASSET_SOURCE_PATH = '../src/modules/player/uiConfig/assets'
const BITMOVIN_JS_ASSET_NAME = 'player-ui.bmjs'
const BITMOVIN_CSS_ASSET_NAME = 'player-ui-css.bmcss'

const BITMOVIN_JS_BUNDLED_FILENAME = 'bitmovinplayer-ui.js'
const BITMOVIN_CSS_BUNDLED_FILENAME = 'bitmovinplayer-ui.css'

const withBitmovinUIResources = (config) => {
  // iOS configuration
  config = withXcodeProject(config, (xcodeProjectConfig) => {
    const projectRoot = xcodeProjectConfig.modRequest.projectRoot
    const iosProjectRoot = path.join(projectRoot, 'ios')

    const sourcePath = path.join(projectRoot, BITMOVIN_ASSET_SOURCE_PATH)
    const jsSource = path.join(sourcePath, BITMOVIN_JS_ASSET_NAME)
    const cssSource = path.join(sourcePath, BITMOVIN_CSS_ASSET_NAME)

    // Destination files in iOS project
    const jsDestination = path.join(iosProjectRoot, BITMOVIN_JS_BUNDLED_FILENAME)
    const cssDestination = path.join(iosProjectRoot, BITMOVIN_CSS_BUNDLED_FILENAME)

    validateSourceFilesExist(jsSource, cssSource)

    // Copy files with correct extensions
    fs.copyFileSync(jsSource, jsDestination)
    fs.copyFileSync(cssSource, cssDestination)

    // Add to app bundle as resources
    const xcodeProject = xcodeProjectConfig.modResults
    xcodeProject.addResourceFile(BITMOVIN_JS_BUNDLED_FILENAME)
    xcodeProject.addResourceFile(BITMOVIN_CSS_BUNDLED_FILENAME)

    return xcodeProjectConfig
  })

  // Android configuration
  config = withDangerousMod(config, [
    'android',
    (androidConfig) => {
      const projectRoot = androidConfig.modRequest.projectRoot
      const androidAssetsDir = path.join(
        androidConfig.modRequest.platformProjectRoot,
        'app/src/main/assets',
      )

      const sourcePath = path.join(projectRoot, BITMOVIN_ASSET_SOURCE_PATH)
      const jsSource = path.join(sourcePath, BITMOVIN_JS_ASSET_NAME)
      const cssSource = path.join(sourcePath, BITMOVIN_CSS_ASSET_NAME)

      const jsDestination = path.join(androidAssetsDir, BITMOVIN_JS_BUNDLED_FILENAME)
      const cssDestination = path.join(androidAssetsDir, BITMOVIN_CSS_BUNDLED_FILENAME)

      validateSourceFilesExist(jsSource, cssSource)

      if (!fs.existsSync(androidAssetsDir)) {
        fs.mkdirSync(androidAssetsDir, { recursive: true })
      }

      fs.copyFileSync(jsSource, jsDestination)
      fs.copyFileSync(cssSource, cssDestination)

      return androidConfig
    },
  ])

  return config
}

const validateSourceFilesExist = (jsSource, cssSource) => {
  if (!fs.existsSync(jsSource)) {
    throw new Error(`Bitmovin UI JS source file ${jsSource} does not exist`)
  }

  if (!fs.existsSync(cssSource)) {
    throw new Error(`Bitmovin UI CSS source file ${cssSource} does not exist`)
  }
}

module.exports = withBitmovinUIResources

```

In the metro config

```auto
config.resolver.assetExts = [...config.resolver.assetExts, 'bmjs', 'bmcss']

```

And in your Expo app config

```auto
  // ...

  plugins: [
    './plugins/withBitmovinUIResources',
  ],

  // ...

```

This worked fine for us. Biggest problem is that we need a native deployment (instead of eas update) for every change 😁
