0Pricing
React Native Academy · บทเรียน

การเขียนปลั๊กอินการกำหนดค่าแรก

สร้างฟังก์ชัน withMyPlugin โดยใช้ตัวช่วยของ @expo/config-plugins เพิ่มฟังก์ชันลงในอาร์เรย์ plugins ของ app.json และรัน expo prebuild เพื่อตรวจสอบผลลัพธ์เนทีฟ

การเขียนปลั๊กอินการกำหนดค่าแรก เป็นบทเรียน React Native Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน React Native Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส React Native Academy มีบทเรียนทั้งหมด 4 บทเรียน

บางส่วนของบทเรียนนี้ยังไม่ได้รับการแปล และแสดงเป็นภาษาอังกฤษ

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.

คำถามที่พบบ่อย

บทเรียน “การเขียนปลั๊กอินการกำหนดค่าแรก” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การเขียนปลั๊กอินการกำหนดค่าแรก” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส React Native Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส React Native Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “การเขียนปลั๊กอินการกำหนดค่าแรก”

สร้างฟังก์ชัน withMyPlugin โดยใช้ตัวช่วยของ @expo/config-plugins เพิ่มฟังก์ชันลงในอาร์เรย์ plugins ของ app.json และรัน expo prebuild เพื่อตรวจสอบผลลัพธ์เนทีฟ คุณปฏิบัติ React Native Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน React Native Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน React Native Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน

บทเรียน “การเขียนปลั๊กอินการกำหนดค่าแรก” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน React Native Academy นี้ได้ไหม

ได้ บทเรียน React Native Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. ปลั๊กอินการกำหนดค่าคืออะไรและควรใช้เมื่อใด
  2. การเขียนปลั๊กอินการกำหนดค่าแรก
  3. การแก้ไข AndroidManifest และ Info.plist
  4. การเผยแพร่ปลั๊กอินการกำหนดค่าเป็นแพ็กเกจ npm
← กลับไปที่ React Native Academy