React Native Academy · Pelajaran

Menulis Plugin Konfigurasi Pertama Anda

Buat fungsi withMyPlugin menggunakan helper @expo/config-plugins, tambahkan ke array plugins dalam app.json, lalu jalankan expo prebuild untuk memverifikasi keluaran native.

Pelajaran 2 dari 413 langkah

Menulis Plugin Konfigurasi Pertama Anda adalah pelajaran React Native Academy gratis di CoddyKit. Ini adalah pelajaran 2 dari 4. Kamu bisa membaca pelajaran lengkapnya di bawah secara gratis — lalu praktikkan langsung di browser dengan editor kode bawaan dan tutor AI 24/7. Ini adalah bagian dari jalur belajar React Native Academy, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus React Native Academy mencakup 4 pelajaran total.

Bagian dari pelajaran ini belum diterjemahkan dan ditampilkan dalam bahasa Inggris.

Project Setup for a Config Plugin

You can write a config plugin as a file inside your project or as a separate npm package. For project-specific plugins, create a plugins/ folder at the project root and add your plugin file there. Reference it in app.json using a relative path. This approach keeps the plugin co-located with the app and requires no publishing step.

# Project structure
your-expo-app/
  plugins/
    withCustomPermissions.js   <- your plugin
  app.json
  App.tsx

# app.json reference
{
  'expo': {
    'plugins': [
      ['./plugins/withCustomPermissions', { 'reason': 'Scan QR codes' }]
    ]
  }
}

The withMyPlugin Function Signature

Every config plugin is a synchronous function that takes two arguments: config (the full Expo config object) and an optional options object passed from app.json. It must return the (modified) config object. You chain Expo's built-in modifier helpers inside this function to make specific changes to native files, then return the result of the last modifier call.

// plugins/withCustomPermissions.js
const { withInfoPlist } = require('@expo/config-plugins');

/**
 * @param {import('@expo/config-plugins').ExpoConfig} config
 * @param {{ reason: string }} options
 */
function withCustomPermissions(config, options = {}) {
  const reason = options.reason || 'This app requires camera access.';

  return withInfoPlist(config, (iosConfig) => {
    iosConfig.modResults['NSCameraUsageDescription'] = reason;
    return iosConfig;
  });
}

module.exports = withCustomPermissions;

Understanding modResults

Inside a mod callback (like the one passed to withInfoPlist), you receive a config object where config.modResults contains the parsed native file as a JavaScript object. For withInfoPlist it is a plain object mirroring the plist dictionary. For withAndroidManifest it is a parsed XML document. You modify modResults in place and return the config — Expo serializes it back to the native file format automatically.

const { withInfoPlist } = require('@expo/config-plugins');

module.exports = function withAppTrackingPermission(config) {
  return withInfoPlist(config, (iosConfig) => {
    // modResults is the parsed Info.plist dictionary
    const plist = iosConfig.modResults;

    // Add multiple keys at once
    plist['NSUserTrackingUsageDescription'] =
      'We use tracking to improve your ad experience.';
    plist['ITSAppUsesNonExemptEncryption'] = false;

    return iosConfig;
  });
};

Modifying AndroidManifest.xml

Use withAndroidManifest to modify AndroidManifest.xml. The modResults is an XML object parsed by the xmldom library. Expo's AndroidConfig helpers provide utility functions like addUsesPermission, addMetaDataItemToMainActivity, and getMainApplication that operate on this XML structure, so you rarely need to manipulate the XML DOM directly.

const { withAndroidManifest, AndroidConfig } = require('@expo/config-plugins');
const { addUsesPermission } = AndroidConfig.Permissions;

module.exports = function withBluetoothPermissions(config) {
  return withAndroidManifest(config, (androidConfig) => {
    const manifest = androidConfig.modResults;

    // Add multiple permissions
    addUsesPermission(manifest, 'android.permission.BLUETOOTH');
    addUsesPermission(manifest, 'android.permission.BLUETOOTH_ADMIN');
    addUsesPermission(manifest, 'android.permission.BLUETOOTH_SCAN');

    return androidConfig;
  });
};

Testing Your Plugin with expo prebuild

Run npx expo prebuild --clean to test your plugin and inspect the generated native files. Use --clean to start fresh. After prebuild, open the generated files to verify your changes were applied. For plist changes check ios/YourApp/Info.plist. For manifest changes check android/app/src/main/AndroidManifest.xml. Fix any issues in the plugin file and re-run prebuild until the output is correct.

# Test the plugin
npx expo prebuild --clean --platform ios

# Verify the change in Info.plist
plutil -p ios/MyApp/Info.plist | grep -A1 Camera
# Output:
#   "NSCameraUsageDescription" => "Scan QR codes."

# For Android
cat android/app/src/main/AndroidManifest.xml | grep BLUETOOTH

Adding Files to the Native Project

Some plugins need to copy files into the native project — for example, a Google Services JSON file or a custom font. Use withDangerousMod for arbitrary filesystem operations during prebuild. This is the escape hatch for changes not covered by other helpers. Use it sparingly and document the side effects clearly.

const { withDangerousMod } = require('@expo/config-plugins');
const path = require('path');
const fs = require('fs');

module.exports = function withGoogleServicesJson(config) {
  return withDangerousMod(config, [
    'android',
    async (dangerousConfig) => {
      const projectRoot = dangerousConfig.modRequest.projectRoot;
      const src = path.join(projectRoot, 'google-services.json');
      const dest = path.join(projectRoot, 'android', 'app', 'google-services.json');

      if (!fs.existsSync(src)) {
        throw new Error('google-services.json not found in project root');
      }
      fs.copyFileSync(src, dest);
      return dangerousConfig;
    },
  ]);
};

Accessing Config Values Inside a Plugin

The config object passed to your plugin contains the full Expo configuration, including values from app.json. You can read config.name, config.version, config.ios.bundleIdentifier, config.android.package, and any extra fields you defined. This lets your plugin be data-driven based on the app config rather than hard-coded values.

module.exports = function withAppMetadata(config) {
  const appName = config.name;
  const bundleId = config.ios?.bundleIdentifier;
  const version = config.version;

  return withInfoPlist(config, (iosConfig) => {
    iosConfig.modResults['CFBundleDisplayName'] = appName;
    // Log for debugging during prebuild
    console.log(
      '[withAppMetadata] Setting display name to:', appName,
      'for bundle:', bundleId, 'v' + version
    );
    return iosConfig;
  });
};

Modifying build.gradle with withAppBuildGradle

To modify the Android app-level build.gradle file, use withAppBuildGradle. The modResults.contents property contains the file as a raw string, and you use string manipulation or regex to insert your changes. This approach is necessary for adding Gradle dependencies, plugin declarations, or custom build configurations that the higher-level helpers do not cover.

const { withAppBuildGradle } = require('@expo/config-plugins');

module.exports = function withGooglePlayServicesPlugin(config) {
  return withAppBuildGradle(config, (gradleConfig) => {
    const contents = gradleConfig.modResults.contents;

    // Add google-services plugin if not already present
    if (!contents.includes('com.google.gms.google-services')) {
      gradleConfig.modResults.contents = contents.replace(
        'apply plugin: "com.android.application"',
        'apply plugin: "com.android.application"\napply plugin: "com.google.gms.google-services"'
      );
    }
    return gradleConfig;
  });
};

Modifying the Xcode Project with withXcodeProject

For changes to the Xcode project file itself — like adding a build phase, linking a native library, or adding a capability — use withXcodeProject. The modResults is an XcodeProject object from the xcode npm package. This is the most complex mod and requires familiarity with the Xcode project format. Prefer higher-level helpers when they exist.

const { withXcodeProject } = require('@expo/config-plugins');

module.exports = function withRunScript(config) {
  return withXcodeProject(config, (xcode) => {
    const project = xcode.modResults;
    const target = project.getFirstTarget().uuid;

    // Add a run script build phase
    project.addBuildPhase(
      [],
      'PBXShellScriptBuildPhase',
      'Bundle React Native code and images',
      target,
      { shellPath: '/bin/sh', shellScript: 'echo Running custom script' }
    );

    return xcode;
  });
};

Entitlements with withEntitlementsPlist

iOS entitlements are capability declarations in a .entitlements file that must match your provisioning profile. Use withEntitlementsPlist to add entitlements like Push Notifications, iCloud, HealthKit, or App Groups. Without the correct entitlements, features silently fail at runtime on device even if the code looks correct.

const { withEntitlementsPlist } = require('@expo/config-plugins');

module.exports = function withPushNotifications(config) {
  return withEntitlementsPlist(config, (iosConfig) => {
    const entitlements = iosConfig.modResults;

    // Enable push notifications capability
    entitlements['aps-environment'] = 'production';

    // App Groups for sharing data with extensions
    if (!entitlements['com.apple.security.application-groups']) {
      entitlements['com.apple.security.application-groups'] = [];
    }
    entitlements['com.apple.security.application-groups'].push(
      'group.com.yourapp.shared'
    );

    return iosConfig;
  });
};

Composing a Complete Plugin

Real-world plugins often need to modify multiple native files across both platforms. Compose helpers in sequence, passing the result of each into the next. Add clear input validation at the top and log progress messages so developers can trace what the plugin changed during prebuild. Return the final config after all modifications.

const {
  withInfoPlist,
  withAndroidManifest,
  withEntitlementsPlist,
  AndroidConfig,
} = require('@expo/config-plugins');

module.exports = function withPaymentSDK(config, opts = {}) {
  if (!opts.merchantId) {
    throw new Error('withPaymentSDK requires a merchantId option.');
  }

  // iOS
  config = withInfoPlist(config, (c) => {
    c.modResults['PaymentMerchantId'] = opts.merchantId;
    return c;
  });

  config = withEntitlementsPlist(config, (c) => {
    c.modResults['com.apple.developer.in-app-payments'] = [opts.merchantId];
    return c;
  });

  // Android
  config = withAndroidManifest(config, (c) => {
    AndroidConfig.Permissions.addUsesPermission(
      c.modResults, 'android.permission.INTERNET'
    );
    return c;
  });

  return config;
};

Quick Check

Test your understanding of React Native Mobile Development concepts from this lesson.

Lesson Recap

In this lesson you learned: how to write a config plugin function that takes config and options and returns the modified config, how to use withInfoPlist, withAndroidManifest, withEntitlementsPlist, and withDangerousMod, and how to test your plugin with expo prebuild --clean. You also saw how to compose multiple mods into a complete cross-platform plugin. Next up we explore modifying AndroidManifest and Info.plist in detail.

Gratis untuk memulai

Belajar JavaScript dengan tutor AI — gratis

Tulis dan jalankan kode asli di browser kamu, dapatkan bantuan instan dari tutor AI 24/7, dan lanjutkan di mana kamu tinggalkan di web atau aplikasi.

Kursus
30
Pelajaran
120

Pertanyaan yang Sering Diajukan

Apakah pelajaran “Menulis Plugin Konfigurasi Pertama Anda” gratis?

Ya — teks lengkap “Menulis Plugin Konfigurasi Pertama Anda” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus React Native Academy, upgrade ke CoddyKit PRO. Kursus React Native Academy mencakup 4 pelajaran total.

Apa yang akan aku pelajari di “Menulis Plugin Konfigurasi Pertama Anda”?

Buat fungsi withMyPlugin menggunakan helper @expo/config-plugins, tambahkan ke array plugins dalam app.json, lalu jalankan expo prebuild untuk memverifikasi keluaran native. Kamu berlatih React Native Academy dengan kode praktik yang langsung kamu jalankan di browser, dan tutor AI 24/7 menjawab pertanyaanmu saat kamu mengerjakan pelajaran ini.

Apakah aku perlu pengalaman untuk memulai React Native Academy?

Tidak diperlukan pengalaman sebelumnya. React Native Academy di CoddyKit dirancang untuk pemula hingga pelajar tingkat lanjut, jadi kamu bisa memulai di sini atau dari awal dan belajar sesuai kecepatan kamu sendiri. Ini adalah pelajaran 2 dari 4.

Berapa lama pelajaran “Menulis Plugin Konfigurasi Pertama Anda” memakan waktu?

Sebagian besar pelajaran CoddyKit memakan waktu sekitar 5–10 menit. Setiap pelajaran ringkas dan interaktif, jadi kamu membuat kemajuan stabil dan melanjutkan dari tempat kamu tinggalkan di web dan aplikasi.

Bisakah aku menulis dan menjalankan kode dalam pelajaran React Native Academy ini?

Ya. Setiap pelajaran React Native Academy menyertakan editor kode bawaan, jadi kamu menulis dan menjalankan kode nyata langsung di browser dan mendapatkan umpan balik AI instan — tidak diperlukan penyiapan lokal.

Semua pelajaran dalam kursus ini

  1. Apa Itu Plugin Konfigurasi dan Kapan Menggunakannya
  2. Menulis Plugin Konfigurasi Pertama Anda
  3. Memodifikasi AndroidManifest dan Info.plist
  4. Mendistribusikan Plugin Konfigurasi sebagai Paket npm
← Kembali ke React Native Academy