Mobile & Edge

Calling WordPress AI Abilities From a Mobile App

⏱ 13 min

There is no WordPress-provided mobile SDK for calling abilities or MCP tools, and this lesson doesn’t pretend one exists. What it covers instead is accurate, general mobile development practice: how a native or cross-platform app makes an authenticated HTTPS request, and, more importantly, where that authentication credential actually lives on the device so it isn’t sitting in a plaintext preference file, decompiled binary, or autofill-shared keychain entry for anyone who gets hold of the phone. The WordPress side of this is unchanged from every previous lesson: an Application Password, sent as Basic Auth, resolved to a real WordPress user.

What you'll learn in this lesson
Why mobile changes the credential storage problem, not the auth mechanism
Same Application Password, a very different threat model than a server.
Secure on-device storage: Keychain on iOS, Keystore on Android
The real platform mechanisms, and the cross-platform libraries that wrap them.
A real fetch pattern from a mobile app
Using a standard HTTP client, no invented WordPress-specific SDK.
Why per-user accounts matter even more on mobile
One compromised device should not mean one compromised shared credential.
Prerequisites

The authentication comparison from Lesson 2, and a mobile project (React Native, Flutter, or native iOS/Android, the pattern here is deliberately framework-agnostic).

Step 1: The threat model actually changes on mobile

A Next.js server’s environment variables live on infrastructure you control, behind your own network boundary. A mobile app ships to a device you don’t control at all, one that can be lost, stolen, jailbroken, or have its storage inspected by another app in some circumstances. Neither WordPress nor the Abilities API changes here, the same Application Password, sent the same way, resolves to the same WP_User. What changes is that “where do I put this credential” is now a genuinely harder, device-level security question, not a configuration convenience.

Never hardcode a credential in the app binary

An Application Password baked directly into app source code, even as a “just for now” placeholder, ships inside the compiled binary and is recoverable by anyone willing to decompile or inspect network traffic from the app. Every credential needs to be provisioned per-install or per-user, never embedded as a constant.

Step 2: Secure on-device storage, the real platform mechanisms

Both major mobile platforms provide a hardware-backed secure storage area specifically for this kind of secret, distinct from ordinary app preferences or local storage:

iOS Keychain Services
Apple's encrypted, access-controlled storage for credentials, separate from regular app files.
Android Keystore
Android's hardware-backed key and credential storage, with per-app isolation.
Cross-platform wrappers
Libraries like react-native-keychain (React Native) or Expo SecureStore (Expo) provide one API over both platforms' native secure storage.

None of these are WordPress-specific, they’re the same mechanisms any mobile app uses to store an API key or session token for any backend. That’s the correct mental model here: your WordPress Application Password is just a credential like any other, and it deserves the same storage discipline as one.

// File: auth/credentials.ts (React Native, using react-native-keychain)
import * as Keychain from 'react-native-keychain';

export async function saveWordPressCredential(username: string, appPassword: string) {
  await Keychain.setGenericPassword(username, appPassword, {
    service: 'wordpress-app-password',
  });
}

export async function getWordPressCredential() {
  const result = await Keychain.getGenericPassword({ service: 'wordpress-app-password' });
  return result ? { username: result.username, appPassword: result.password } : null;
}
Regular AsyncStorage or SharedPreferences is not secure storage

AsyncStorage in React Native, or plain SharedPreferences on Android, store data unencrypted on most devices. They’re fine for UI preferences, not for an authentication credential. If you see an Application Password going into either of those, that’s a real vulnerability, move it into Keychain/Keystore-backed storage instead.

Step 3: A real fetch pattern, no invented SDK required

Calling an ability from the device is the same HTTP request as every previous lesson, just made with whatever standard HTTP client your mobile stack already uses:

// File: api/abilities.ts (React Native, fetch)
import { getWordPressCredential } from '../auth/credentials';

export async function getLatestPostTitle(baseUrl: string) {
  const cred = await getWordPressCredential();
  if (!cred) {
    throw new Error('No WordPress credential stored on this device');
  }

  const auth = btoa(`${cred.username}:${cred.appPassword}`);

  const res = await fetch(`${baseUrl}/wp-json/mcp/v1/my-plugin/get-latest-post-title`, {
    method: 'POST',
    headers: {
      Authorization: `Basic ${auth}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({}),
  });

  if (!res.ok) {
    throw new Error(`Ability call failed with status ${res.status}`);
  }

  return res.json() as Promise<{ title: string }>;
}

Nothing here is a WordPress mobile SDK, it’s a standard fetch call with a credential pulled from secure storage. That’s the accurate, honest shape of a WordPress mobile integration today: general HTTP client code, plus WordPress’s existing REST and Application Password surface.

Step 4: Why per-user accounts matter even more here

Course 4 recommended dedicated, least-privilege WordPress accounts for service integrations. On mobile, this matters more, not less: if each installed copy of your app authenticates as the same single shared Application Password, one compromised device compromises every user’s access simultaneously. Where practical, provision credentials per end user (each with their own WordPress account and Application Password, or their own JWT session if you made that deliberate tradeoff in Lesson 2) rather than baking one shared service credential into every install of a consumer app.

A shared credential across every install is a single point of failure

If your abilities allow anything beyond read-only, publishing, editing, deleting, a single shared credential embedded in a widely distributed app is a materially worse risk than the same credential used only by your own server. Scope mobile-facing abilities’ permission_callback tightly, and prefer per-user credentials whenever the app has any concept of individual user accounts at all.

Test it: confirm the credential survives an app restart, but only in secure storage

Save a credential, force-quit the app, relaunch it, and confirm the stored credential is still retrievable through your Keychain/Keystore wrapper, not through a plain preferences read:

// Quick manual check in a debug screen
const cred = await getWordPressCredential();
console.log('Retrieved:', cred ? 'yes' : 'no');

Then confirm you cannot find the raw Application Password string anywhere in the app’s plain preferences file or local storage directory, only inside the platform’s secure storage.

Recap

A mobile app calling WordPress abilities uses the exact same Application Password and Basic Auth mechanism as every other client in this course, there is no separate WordPress mobile SDK, and none is needed. What mobile changes is the storage question: the credential belongs in the iOS Keychain or Android Keystore, accessed through a wrapper like react-native-keychain or Expo SecureStore, never in plain preferences or hardcoded in source. For anything beyond read-only access, prefer per-user credentials over one shared secret baked into every install. The next lesson moves to a different constrained environment, edge and serverless functions, and where the same secret belongs there.

Resources & further reading

← Building a Typed JS Client for Your Abilities Edge and Serverless Considerations →