jutsu-expo
Validation and quality enforcement for Expo React Native projects with config, router, modules, updates, and build skills.
View on GitHubTable of content
Validation and quality enforcement for Expo React Native projects with config, router, modules, updates, and build skills.
Installation
npx claude-plugins install @TheBushidoCollective/han/jutsu-expo
Contents
Folders: skills
Files: CHANGELOG.md, README.md
Documentation
Validation and quality enforcement for Expo React Native projects.
What This Jutsu Provides
Skills
This jutsu provides the following skills:
- expo-config: Configuring Expo apps with app.json, app.config.js/ts, and EAS configuration including plugins, build settings, and environment variables
- expo-router: Implementing file-based routing with Expo Router including layouts, dynamic routes, navigation, and deep linking
- expo-modules: Working with Expo SDK modules for camera, location, notifications, file system, and other device APIs
- expo-updates: Implementing over-the-air (OTA) updates with Expo Updates for deploying changes without app store releases
- expo-build: Building and deploying Expo apps with EAS Build including development builds, production builds, and app store submission
Installation
Install with npx (no installation required):
han plugin install jutsu-expo
Requirements
- Expo SDK 50 or higher
- TypeScript 5.0 or higher
- Node.js 18 or higher
Why Expo?
Expo is a framework and platform for React Native that makes mobile development faster and easier:
- Managed Workflow: Build apps without touching native code
- EAS (Expo Application Services): Cloud build, updates, and submission
- Expo Router: File-based routing system
- Rich SDK: 50+ modules for device features (camera, location, etc.)
- OTA Updates: Deploy updates instantly without app store review
- Universal Apps: Share code across iOS, Android, and web
- Developer Experience: Fast refresh, debugging tools, and documentation
Features Covered by Skills
Configuration (app.json/app.config.ts)
- App metadata and settings
- Platform-specific configuration
- Plugin system
- Environment variables
- Build profiles
- Deep linking setup
Expo Router
- File-based routing
- Stack, tab, and drawer navigation
- Dynamic routes with parameters
- Layouts and nested routes
- Route groups
- Authentication flows
- Modal routes
- Type-safe navigation
Expo Modules (SDK)
- Camera and image picker
- Location and maps
- Push notifications
- File system and storage
- Secure storage
- Device information
- Background tasks
- Sharing and permissions
OTA Updates
- Automatic update checking
- Manual update triggers
- Update channels
- Runtime versions
- Silent updates
- Update UI patterns
EAS Build
- Development builds
- Preview and production builds
- Environment configuration
- Secrets management
- Version management
- App store submission
- Build monitoring
Quick Start Examples
Create New Expo App
npx create-expo-app@latest my-app
cd my-app
Basic Expo Router App
// app/_layout.tsx
import { Stack } from 'expo-router';
export default function RootLayout() {
return <Stack />;
}
// app/index.tsx
import { Link } from 'expo-router';
import { View, Text } from 'react-native';
export default function Home() {
return (
<View>
<Text>Home Screen</Text>
<Link href="/details">Go to Details</Link>
</View>
);
}
// app/details.tsx
export default function Details() {
return (
<View>
<Text>Details Screen</Text>
</View>
);
}
Using Expo Modules
import { Camera } from 'expo-camera';
import * as Location from 'expo-location';
import * as Notifications from 'expo-notifications';
// Request permissions and use modules
const [cameraPermission] = Camera.useCameraPermissions();
Configure for Production
// app.config.ts
export default {
name: 'MyApp',
slug: 'my-app',
version: '1.0.0',
extra: {
apiUrl: process.env.API_URL,
},
plugins: [
'expo-router',
'expo-camera',
'expo-location',
],
};
Build with EAS
# Install EAS CLI
npm install -g eas-cli
# Login
eas login
# Configure project
eas build:configure
# Build for iOS
eas build --platform ios --profile production
# Build for Android
eas build --platform android --profile production
Project Structure
Recommended structure for Expo apps with Expo Router:
my-app/
├── app/ # Routes (file-based routing)
│ ├── (tabs)/ # Tab navigation group
│ │ ├── _layout.tsx
│ │ ├── index.tsx
│ │ └── profile.tsx
│ ├── _layout.tsx # Root layout
│ └── index.tsx # Home route
├── components/ # Reusable components
├── hooks/ # Custom hooks
├── utils/ # Utility functions
├── assets/ # Images, fonts, etc.
├── app.json # Expo configuration
├── eas.json # EAS Build configuration
├── package.json
└── tsconfig.json
Common Expo Modules
Essential modules for most Expo apps:
- expo-router: File-based navigation
- expo-camera: Camera access
- expo-location: Location services
- expo-notifications: Push notifications
- expo-image-picker: Select images/videos
- expo-file-system: File operations
- **expo-secure-stor
…(truncated)
Included Skills
This plugin includes 5 skill definitions:
expo-build
Use when building and deploying Expo apps with EAS Build. Covers build configuration, development builds, production builds, and app store submission.
View skill definition
Expo Build with EAS
Use this skill when building and deploying Expo applications using EAS (Expo Application Services) Build.
Key Concepts
EAS Build Configuration
// eas.json
{
"cli": {
"version": ">= 5.9.0"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal",
"ios": {
"simulator": true
}
},
"preview": {
"distribution": "internal"
},
"production": {
"autoIncrement": true
}
},
"submit": {
"production": {}
}
}
Build Commands
# Development build
eas build --profile development --platform ios
eas build --profile development --platform android
# Preview build
eas build --profile preview --platform all
# Production build
eas build --profile production --platform all
# Local build
eas build --profile production --platform ios --local
Development Builds
{
"build": {
"development": {
"developmentClient": true,
"distribution": "internal",
"env": {
"NODE_ENV": "development"
}
}
}
}
Best Practices
Environment Variables
// eas.json
{
"build": {
"production": {
"env": {
"APP_ENV": "production",
"API_URL": "https://api.myapp.com"
}
},
"staging": {
"env": {
"APP_ENV": "staging",
"API_URL": "https://staging-api.myapp.com"
}
}
}
}
Secrets Management
# Set sec
...(truncated)
</details>
### expo-config
> Use when configuring Expo apps with app.json, app.config.js, and EAS configuration. Covers app metadata, plugins, build configuration, and environment variables.
<details>
<summary>View skill definition</summary>
# Expo Configuration
Use this skill when configuring Expo applications using app.json, app.config.js/ts, and EAS (Expo Application Services) configuration files.
## Key Concepts
### app.json Configuration
Basic static configuration:
```json
{
"expo": {
"name": "MyApp",
"slug": "my-app",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "automatic",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"assetBundlePatterns": [
"**/*"
],
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.mycompany.myapp",
"buildNumber": "1"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"package": "com.mycompany.myapp",
"versionCode": 1
},
"web": {
"favicon": "./assets/favicon.png"
}
}
}
Dynamic Configuration (app.config.js)
Use JavaScript for dynamic configuration:
export default ({ config }) => ({
...config,
name: process.env.APP_NAME || 'MyApp',
slug: 'my-app',
version: '1.0.0',
extra: {
apiUrl: process.env.API_URL,
environment: process.env.NODE_ENV,
},
ios: {
bundleIdentifier:
process.env.NODE_ENV === 'production'
? 'com.mycompany.myapp'
: 'com.mycompany.myapp.dev',
},
android: {
packag
...(truncated)
</details>
### expo-modules
> Use when working with Expo SDK modules for camera, location, notifications, file system, and other device APIs. Covers permissions, configurations, and best practices.
<details>
<summary>View skill definition</summary>
# Expo Modules
Use this skill when working with Expo's extensive SDK modules for accessing device features and native functionality.
## Key Concepts
### Camera
```tsx
import { Camera, CameraType } from 'expo-camera';
import { useState } from 'react';
import { Button, View } from 'react-native';
export default function CameraScreen() {
const [permission, requestPermission] = Camera.useCameraPermissions();
const [type, setType] = useState(CameraType.back);
if (!permission?.granted) {
return (
<View>
<Button title="Grant Permission" onPress={requestPermission} />
</View>
);
}
return (
<Camera style={{ flex: 1 }} type={type}>
<Button
title="Flip Camera"
onPress={() =>
setType(type === CameraType.back ? CameraType.front : CameraType.back)
}
/>
</Camera>
);
}
Location
import * as Location from 'expo-location';
import { useEffect, useState } from 'react';
export function useLocation() {
const [location, setLocation] = useState<Location.LocationObject | null>(null);
useEffect(() => {
(async () => {
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') return;
const loc = await Location.getCurrentPositionAsync({});
setLocation(loc);
})();
}, []);
return location;
}
Notifications
import * as Notifications from 'expo-notifications';
import { useEffect } from 'react';
...(truncated)
</details>
### expo-router
> Use when implementing file-based routing in Expo with Expo Router. Covers app directory structure, navigation, layouts, dynamic routes, and deep linking.
<details>
<summary>View skill definition</summary>
# Expo Router
Use this skill when implementing file-based routing with Expo Router, the recommended navigation solution for Expo apps.
## Key Concepts
### File-Based Routing
Routes are defined by file structure:
app/ _layout.tsx # Root layout index.tsx # / route about.tsx # /about route (tabs)/ # Group (not in URL) _layout.tsx # Tabs layout home.tsx # /home profile.tsx # /profile users/ [id].tsx # /users/:id dynamic route index.tsx # /users route
### Basic Routes
```tsx
// app/index.tsx
import { View, Text } from 'react-native';
import { Link } from 'expo-router';
export default function Home() {
return (
<View>
<Text>Home Screen</Text>
<Link href="/about">Go to About</Link>
</View>
);
}
// app/about.tsx
export default function About() {
return (
<View>
<Text>About Screen</Text>
</View>
);
}
Layouts
// app/_layout.tsx
import { Stack } from 'expo-router';
export default function RootLayout() {
return (
<Stack>
<Stack.Screen name="index" options={{ title: 'Home' }} />
<Stack.Screen name="about" options={{ title: 'About' }} />
</Stack>
);
}
Tab Navigation
// app/(tabs)/_layout.tsx
import { Tabs } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';
export default function TabLayout() {
return (
<Tabs>
<Tabs.Screen
...(truncated)
</details>
### expo-updates
> Use when implementing over-the-air (OTA) updates with Expo Updates. Covers update configuration, checking for updates, and update strategies.
<details>
<summary>View skill definition</summary>
# Expo Updates
Use this skill when implementing over-the-air (OTA) updates to deploy JavaScript and asset updates without app store releases.
## Key Concepts
### Configuration
```json
// app.json
{
"expo": {
"updates": {
"enabled": true,
"checkAutomatically": "ON_LOAD",
"fallbackToCacheTimeout": 0,
"url": "https://u.expo.dev/[project-id]"
},
"runtimeVersion": {
"policy": "sdkVersion"
}
}
}
Checking for Updates
import * as Updates from 'expo-updates';
import { useEffect, useState } from 'react';
import { View, Text, Button } from 'react-native';
export default function App() {
const [updateAvailable, setUpdateAvailable] = useState(false);
useEffect(() => {
async function checkForUpdates() {
if (!__DEV__) {
const update = await Updates.checkForUpdateAsync();
setUpdateAvailable(update.isAvailable);
}
}
checkForUpdates();
}, []);
const handleUpdate = async () => {
const { isNew } = await Updates.fetchUpdateAsync();
if (isNew) {
await Updates.reloadAsync();
}
};
if (updateAvailable) {
return (
<View>
<Text>Update Available!</Text>
<Button title="Update Now" onPress={handleUpdate} />
</View>
);
}
return <View>{/* Your app */}</View>;
}
Runtime Versions
// app.config.ts
export default {
expo: {
runtimeVersion: {
policy: 'appVersion', // Match app version
},
...(truncated)
</details>
## Source
[View on GitHub](https://github.com/TheBushidoCollective/han)