> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pnlight.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Remote Config

> Fetch typed PNLight Remote Config values from Swift, React Native, and Flutter apps with local defaults and safe fallbacks.

PNLight Remote Config delivers typed app settings from the dashboard to the SDK. Use it for non-sensitive runtime behavior such as feature flags, copy variants, limits, and paywall settings.

Remote Config is separate from [Remote UI](/sdk/remote-ui). Remote UI returns renderable screen configs. Remote Config returns typed values that your app reads and applies in its own code.

<Warning>
  Do not store secrets in Remote Config. Never put API keys, credentials, shared secrets, or other sensitive values in Remote Config.
</Warning>

## Configure values in the dashboard

Create the base configuration, add targeted overrides, and publish the draft in **Remote Config**. See [Remote Config dashboard setup](/dashboard/remote-config) for the workflow.

Supported top-level value types:

* Boolean.
* String.
* Number.
* String array.
* JSON.

Keys are flat. If an override changes a JSON value, it replaces the whole value instead of deep-merging nested fields.

## Initialize with local defaults

Provide app-owned defaults during SDK initialization. Defaults stay on the device and are never sent to PNLight. The SDK uses them before the first successful fetch, when offline, or when a remote value is missing or has a different type.

<CodeGroup>
  ```swift Swift theme={null}
  import PNLightSDK

  let config = PNLightConfig(remoteConfigDefaults: [
      "paywall_enabled": .boolean(false),
      "welcome_title": .string("Welcome"),
      "trial_days": .number(7),
      "enabled_plans": .stringArray(["monthly"]),
      "paywall_style": .jsonObject([
          "accent": .string("#6750A4"),
          "compact": .boolean(false),
      ]),
  ])

  await PNLightSDK.shared.initialize(apiKey: "pnlight_sdk_token", config: config)
  ```

  ```tsx React Native theme={null}
  import { initialize } from "@pnlight/sdk-react-native";

  await initialize("pnlight_sdk_token", undefined, {
    paywall_enabled: false,
    welcome_title: "Welcome",
    trial_days: 7,
    enabled_plans: ["monthly"],
    paywall_style: {
      accent: "#6750A4",
      compact: false,
    },
  });
  ```

  ```dart Flutter theme={null}
  import 'package:pnlight_sdk/pnlight_sdk.dart';

  await PNLightSDK.initialize(
    'pnlight_sdk_token',
    remoteConfigDefaults: {
      'paywall_enabled': false,
      'welcome_title': 'Welcome',
      'trial_days': 7,
      'enabled_plans': ['monthly'],
      'paywall_style': {
        'accent': '#6750A4',
        'compact': false,
      },
    },
  );
  ```
</CodeGroup>

## Fetch and activate

Call `fetchAndActivate` after initialization when your app is ready to refresh Remote Config.

By default, the SDK:

* Waits briefly for AppsFlyer attribution so PNLight can apply attribution-based overrides.
* Throttles successful fetches for 15 minutes.
* Persists the active configuration per SDK token and PNLight user.
* Keeps the previously active configuration if a fetch fails.

Use an immediate fetch only for development, QA, or a startup path that must not wait for attribution.

<CodeGroup>
  ```swift Swift theme={null}
  let result = await PNLightSDK.shared.fetchAndActivate()

  switch result {
  case .activated, .notModified:
      break
  case .throttled:
      break
  case .failed:
      break
  }

  let immediateResult = await PNLightSDK.shared.fetchAndActivate(
      minimumFetchInterval: 0,
      waitAttribution: false
  )
  ```

  ```tsx React Native theme={null}
  import { fetchAndActivate } from "@pnlight/sdk-react-native";

  try {
    const result = await fetchAndActivate();
    // "activated", "notModified", or "throttled"
    console.log("Remote Config fetch:", result);
  } catch (error) {
    // Previously active values and local defaults remain available.
    console.warn("Remote Config unavailable", error);
  }

  await fetchAndActivate(0, false);
  ```

  ```dart Flutter theme={null}
  final result = await PNLightSDK.fetchAndActivate();

  switch (result) {
    case RemoteConfigFetchResult.activated:
    case RemoteConfigFetchResult.notModified:
      break;
    case RemoteConfigFetchResult.throttled:
      break;
    case RemoteConfigFetchResult.failed:
      break;
  }

  await PNLightSDK.fetchAndActivate(
    minimumFetchInterval: Duration.zero,
    waitAttribution: false,
  );
  ```
</CodeGroup>

## Read typed values

Use typed getters with fallbacks. If a key is missing or the active value has a different type, the SDK returns the fallback.

<CodeGroup>
  ```swift Swift theme={null}
  let paywallEnabled = PNLightSDK.shared.remoteConfigBoolean(
      forKey: "paywall_enabled",
      fallback: false
  )
  let title = PNLightSDK.shared.remoteConfigString(
      forKey: "welcome_title",
      fallback: "Welcome"
  )
  let trialDays = PNLightSDK.shared.remoteConfigNumber(
      forKey: "trial_days",
      fallback: 7
  )
  let plans = PNLightSDK.shared.remoteConfigStringArray(
      forKey: "enabled_plans",
      fallback: ["monthly"]
  )
  let style = PNLightSDK.shared.remoteConfigJSONObject(
      forKey: "paywall_style",
      fallback: ["accent": .string("#6750A4")]
  )
  ```

  ```tsx React Native theme={null}
  import {
    getRemoteConfigBoolean,
    getRemoteConfigJSONObject,
    getRemoteConfigNumber,
    getRemoteConfigString,
    getRemoteConfigStringArray,
  } from "@pnlight/sdk-react-native";

  const paywallEnabled = await getRemoteConfigBoolean("paywall_enabled", false);
  const title = await getRemoteConfigString("welcome_title", "Welcome");
  const trialDays = await getRemoteConfigNumber("trial_days", 7);
  const plans = await getRemoteConfigStringArray("enabled_plans", ["monthly"]);
  const style = await getRemoteConfigJSONObject("paywall_style", {
    accent: "#6750A4",
  });
  ```

  ```dart Flutter theme={null}
  final paywallEnabled = await PNLightSDK.remoteConfigBoolean(
    'paywall_enabled',
    fallback: false,
  );
  final title = await PNLightSDK.remoteConfigString(
    'welcome_title',
    fallback: 'Welcome',
  );
  final trialDays = await PNLightSDK.remoteConfigNumber(
    'trial_days',
    fallback: 7,
  );
  final plans = await PNLightSDK.remoteConfigStringArray(
    'enabled_plans',
    fallback: ['monthly'],
  );
  final style = await PNLightSDK.remoteConfigJSONObject(
    'paywall_style',
    fallback: {'accent': '#6750A4'},
  );
  ```
</CodeGroup>

## Recommended use

* Keep app-critical defaults in the app so first launch and offline sessions work.
* Read Remote Config through typed getters instead of casting raw JSON.
* Use `minimumFetchInterval: 0` only in development or QA.
* Fetch after forwarding attribution when campaign-specific overrides matter.
* Coordinate key names and value types between dashboard owners and app developers before publishing.
