# Integrate Luciq with Expo

### Overview

Luciq supports modern Expo development. The recommended way to install Luciq is by using our **Expo Plugin**, which automatically handles native project configuration and automates the sourcemap uploading process for your standard builds.

{% hint style="info" %}
The Luciq SDK is not supported in the Expo Go app. You will need to create a development build or use a simulator/physical device.
{% endhint %}

***

### Recommended Setup: Using the Expo Plugin

This is the simplest and most robust way to integrate Luciq into an Expo project.

{% stepper %}
{% step %}
**Install the SDK**

In your project directory, install the Luciq SDK.

{% tabs %}
{% tab title="npm" %}
{% code title="Install (npm)" %}

```
npm install @luciq/react-native
```

{% endcode %}
{% endtab %}

{% tab title="yarn" %}
{% code title="Install (yarn)" %}

```
yarn add @luciq/react-native
```

{% endcode %}
{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}
**Configure the Expo Plugin**

Add the `@luciq/react-native` plugin to the `plugins` array in your `app.json` file.

{% code title="app.json" %}

```json
{
  "expo": {
    "plugins": [
      [
        "@luciq/react-native",
        {
          "addScreenRecordingBugReportingPermission": true //check note below
        }
      ]
    ]
  }
}
```

{% endcode %}

The `addScreenRecordingBugReportingPermission` is an optional helper that automatically adds the required microphone and photo library permissions on iOS and the foreground service permission on Android for the screen recording feature.
{% endstep %}

{% step %}
**Rebuild Your App**

After adding the plugin, rebuild your app's native directories for the changes to take effect.

{% code title="Rebuild (local)" %}

```
npx expo prebuild
```

{% endcode %}

{% hint style="info" %}
This command is for local development. If you are using EAS Build, this step is handled automatically for you during the build process.

This command will modify your `ios` and `android` directories to include the necessary Luciq configurations.
{% endhint %}
{% endstep %}

{% step %}
**Initialize the SDK**

Import and initialize Luciq in your app's main file (e.g. `App.js`):

```javascript
import Luciq, { InvocationEvent } from '@luciq/react-native';

Luciq.init({
  token: 'APP_TOKEN',
  invocationEvents: [InvocationEvent.shake],
});
```

You can find your app token by selecting **SDK Integration** in the **Settings** menu from your Luciq dashboard.
{% endstep %}
{% endstepper %}

***

### Automatic Sourcemap Uploads

A key benefit of using the Expo Plugin is that it **automatically handles sourcemap uploads** for all your standard Expo builds (`eas build`). By adding the plugin to your `app.json`, it will detect your builds and upload the correct sourcemaps to Luciq with no additional configuration required.

{% hint style="info" %}
This automatic process applies to standard builds only. For **Expo Updates (OTA)**, you must manually upload sourcemaps using our CLI. See our [Over-the-Air (OTA) Updates Guide](broken://pages/91aec8e190d6087ac41fe8c8b0c67cbcb76a3496) for more details.
{% endhint %}

***

### OTA updates (`expo-updates`) <a href="#ota-updates-expo-updates" id="ota-updates-expo-updates"></a>

#### Supported <a href="#supported" id="supported"></a>

`Luciq.setOverAirVersion()` attaches the active OTA update identifier to all events:

```javascript
import * as Updates from 'expo-updates';
import Luciq, { OverAirUpdateServices } from '@luciq/react-native';
Luciq.setOverAirVersion({
  service: OverAirUpdateServices.expo,
  version: Updates.updateId ?? 'embedded',
});
```

Once set, every report, crash, and APM span carries the update version.

#### Enriching with channel, runtime version, and emergency-launch state <a href="#enriching-with-channel-runtime-version-and-emergency-launch-state" id="enriching-with-channel-runtime-version-and-emergency-launch-state"></a>

These fields are not auto-collected. Add them as user attributes and tags so you can filter on them in the dashboard:

```javascript
import * as Updates from 'expo-updates';
import Luciq from '@luciq/react-native';

Luciq.setUserAttribute('expo.updates.channel', Updates.channel ?? 'unknown');
Luciq.setUserAttribute('expo.updates.runtime_version', Updates.runtimeVersion ?? 'unknown');
Luciq.setUserAttribute('expo.updates.is_embedded', String(Updates.isEmbeddedLaunch));

if (Updates.isEmergencyLaunch) {
  Luciq.appendTags(['expo.updates.emergency_launch']);
  Luciq.setUserAttribute(
    'expo.updates.emergency_launch_reason',
    Updates.emergencyLaunchReason ?? 'unknown',
  );
  Luciq.logUserEvent('expo_updates_emergency_launch');
}
```

Call this once after `Luciq.init()`, ideally inside your root component or in `App.tsx`.

***

### Expo execution environment context (`expo-constants`) <a href="#expo-execution-environment-context-expo-constants" id="expo-execution-environment-context-expo-constants"></a>

Expo Constants metadata (`Expo Go` vs `standalone` vs `bare`, EAS project ID, app config name/version, SDK version) is not collected automatically. Forward it as user attributes:

```javascript
import Constants from 'expo-constants';
import Luciq from '@Luciq/react-native';

Luciq.setUserAttribute('expo.execution_environment', Constants.executionEnvironment);
Luciq.setUserAttribute('expo.sdk_version', Constants.expoConfig?.sdkVersion ?? '');
Luciq.setUserAttribute('expo.eas_project_id', Constants.expoConfig?.extra?.eas?.projectId ?? '');
Luciq.setUserAttribute('expo.app_name', Constants.expoConfig?.name ?? '');
Luciq.setUserAttribute('expo.app_version', Constants.expoConfig?.version ?? '');
```

***

### Expo Router (`expo-router`) <a href="#expo-router-expo-router" id="expo-router-expo-router"></a>

#### Supported <a href="#supported.1" id="supported.1"></a>

Screen-load APM spans fire automatically when a route renders, regardless of whether you got there via `router.push`, `router.replace`, or a deep link. No extra code is needed - this is part of the standard navigation integration.

#### Tracking `router.prefetch()` <a href="#tracking-router.prefetch" id="tracking-router.prefetch"></a>

`router.prefetch()` does not surface in screen-load timings because no screen mounts. To measure prefetch cost, wrap it with a custom APM span:

```javascript
import { useRouter } from 'expo-router';
import { APM } from '@luciq/react-native';

export function useTracedRouter() {
  const router = useRouter();
  return {
    ...router,
    prefetch: async (href: string) => {
      const span = await APM.startCustomSpan(`navigation.prefetch:${href}`);
      try {
        return await router.prefetch(href);
      } finally {
        await span?.end();
      }
    },
  };
}
```

Use useTracedRouter() instead of useRouter() anywhere you call prefetch.

***

### Image and asset loading (`expo-image`, `expo-asset`) <a href="#image-and-asset-loading-expo-image-expo-asset" id="image-and-asset-loading-expo-image-expo-asset"></a>

Network requests for images are already captured by Luciq's network logger. The cache and decoding work that happens after the request - which is what `Image.prefetch`, `Image.loadAsync`, and `Asset.loadAsync` do - is not auto-instrumented.

To time these calls, wrap them with custom spans:

```javascript
import { Image } from 'expo-image';
import { APM } from '@luciq/react-native';

export async function tracedImagePrefetch(urls: string | string[]) {
  const span = await APM.startCustomSpan('expo-image.prefetch');
  try {
    return await Image.prefetch(urls);
  } finally {
    await span?.end();
  }
}
```

The same pattern applies to Image.loadAsync and Asset.loadAsync. Keep span names stable so they aggregate cleanly in the APM dashboard.

***

### EAS Build pipeline visibility <a href="#eas-build-pipeline-visibility" id="eas-build-pipeline-visibility"></a>

EAS builds run on remote infrastructure outside the app, so the SDK cannot observe them. There is no Luciq-side hook for `eas-build-pre-install`, `eas-build-on-success`, or `eas-build-on-error`.

***

### Legacy: Using a Custom Development Client

For older managed workflows that do not use the Expo plugin system, you will need to use Expo’s custom development client.

{% stepper %}
{% step %}
Run the dev client package install:

{% code title="Install expo-dev-client" %}

```
npx expo install expo-dev-client
```

{% endcode %}
{% endstep %}

{% step %}
Modify your `package.json` scripts to use the `--dev-client` flag for the start command.

{% code title="package.json (scripts)" %}

```json
"scripts": {
  "start": "expo start --dev-client",
  "android": "expo run:android",
  "ios": "expo run:ios"
}
```

{% endcode %}
{% endstep %}

{% step %}
Proceed with the Luciq SDK installation and initialization as described above.
{% endstep %}
{% endstepper %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.luciq.ai/react-native/setup-luciq-for-react-native/integrate-luciq-on-react-native/integrate-luciq-with-expo.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
