Begin your journey with the App Programming Guidelines for CarPlay, a comprehensive 50-page manual by Apple detailing the essentials for CarPlay apps.
For additional details while developing or contributing, refer to the CarPlay Documentation.
🚀 Quickstart: Utilize the simulator to test CarPlay capabilities without waiting for Apple's entitlement approval.
🔑 Entitlements: To deploy on a device or distribute via App Store Connect or TestFlight, obtain a CarPlay entitlement here. The approval duration varies, and participation in the MFi program may expedite the process. Incorporate the entitlement into your app's provisioning profile in Xcode.
🖥 Simulator: In Xcode, navigate to the Simulator window, choose IO > External Displays > CarPlay to launch the CarPlay simulator.
Ensure your Entitlements.plist within the iOS/ directory contains the correct entitlement key, whether for simulation or actual deployment.
You need to convert your project to using Scenes, as this is the standard when managing multiple windows in iOS 13+. This is a requirement for CarPlay apps.
This has been proven to work on Expo SDK 53 without CNG. Your results may vary depending on your environment, but you should get an idea of the required steps anyway. :)
This is where your app will run on the phone. Make sure to provide the proper Swift header on the YOURAPP-Swift import!
PhoneScene.h
//
// PhoneScene.h
// ABRP
//
// Created by Manuel Auer on 17.10.24.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <React/RCTRootView.h>
#import <React/RCTLinkingManager.h>
#import "LaunchURLStorage.h"
#import "YOURAPP-Swift.h"
@interface PhoneSceneDelegate : UIResponder <UIWindowSceneDelegate>
@property (strong, nonatomic) UIWindow *window;
@end
PhoneScene.m
//
// PhoneScene.m
// ABRP
//
// Created by Manuel Auer on 17.10.24.
//
#import "PhoneScene.h"
#import "ExpoScreenOrientation-Swift.h"
@implementation PhoneSceneDelegate
- (void)scene:(UIScene *)scene
willConnectToSession:(UISceneSession *)session
options:(UISceneConnectionOptions *)connectionOptions {
if (![session.role isEqualToString:UIWindowSceneSessionRoleApplication]) {
return;
}
AppDelegate *appDelegate = (AppDelegate *)UIApplication.sharedApplication.delegate;
if (!appDelegate) {
return;
}
if (![scene isKindOfClass:[UIWindowScene class]]) {
return;
}
UIWindowScene *windowScene = (UIWindowScene *)scene;
self.window = [[UIWindow alloc] initWithWindowScene:windowScene];
self.window.rootViewController = appDelegate.window.rootViewController;
[[ScreenOrientationRegistry shared] updateCurrentScreenOrientation];
[self.window makeKeyAndVisible];
UIOpenURLContext *urlContext = connectionOptions.URLContexts.allObjects.firstObject;
if (urlContext) {
// Linking API -> app isn’t running
[LaunchURLStorage.shared setLaunchURL:urlContext.URL];
}
for (NSUserActivity *userActivity in connectionOptions.userActivities) {
if (userActivity.webpageURL) {
// Universal Links -> app isn’t running
[LaunchURLStorage.shared setLaunchURL:userActivity.webpageURL];
break;
}
}
}
// Linking API -> after launch
- (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts {
UIOpenURLContext *urlContext = URLContexts.allObjects.firstObject;
if (!urlContext) {
return;
}
NSURL *url = urlContext.URL;
NSDictionary<UIApplicationOpenURLOptionsKey, id> *options = @{
UIApplicationOpenURLOptionsSourceApplicationKey: urlContext.options.sourceApplication ?: @"",
UIApplicationOpenURLOptionsAnnotationKey: urlContext.options.annotation ?: @""
};
[RCTLinkingManager application:UIApplication.sharedApplication openURL:url options:options];
}
// Universal Links -> after launch
- (void)scene:(UIScene *)scene continueUserActivity:(NSUserActivity *)userActivity {
AppDelegate *appDelegate = (AppDelegate *)UIApplication.sharedApplication.delegate;
if (!appDelegate) {
return;
}
[RCTLinkingManager application:UIApplication.sharedApplication continueUserActivity:userActivity restorationHandler:^(NSArray * _Nullable _) {}];
[appDelegate application:UIApplication.sharedApplication continueUserActivity:userActivity restorationHandler:^(NSArray * _Nullable _) {}];
}
@endThis is where your app will run on CarPlay.
CarScene.h
//
// CarScene.h
// ABRP
//
// Created by Manuel Auer on 17.10.24.
//
#import <CarPlay/CarPlay.h>
#import <Foundation/Foundation.h>
#import "RNCarPlay.h"
@interface CarSceneDelegate : UIResponder <CPTemplateApplicationSceneDelegate>
@endCarScene.m
//
// CarScene.m
// ABRP
//
// Created by Manuel Auer on 17.10.24.
//
#import "CarScene.h"
@implementation CarSceneDelegate
- (void)templateApplicationScene:(CPTemplateApplicationScene *)templateApplicationScene
didConnectInterfaceController:(CPInterfaceController *)interfaceController {
[RNCarPlay connectWithInterfaceController:interfaceController window:templateApplicationScene.carWindow];
}
- (void)templateApplicationScene:(CPTemplateApplicationScene *)templateApplicationScene
didDisconnectInterfaceController:(CPInterfaceController *)interfaceController {
[RNCarPlay disconnect];
}
- (void)sceneDidEnterBackground:(UIScene *)scene {
[RNCarPlay stateChanged:false];
}
- (void)sceneWillEnterForeground:(UIScene *)scene {
[RNCarPlay stateChanged:true];
}
@endThis is what will run on the cars cluster screen (speedometer)
ClusterScene.h
//
// ClusterScene.h
// ABRP
//
// Created by Manuel Auer on 07.11.24.
//
#import <CarPlay/CarPlay.h>
#import "RNCarPlay.h"
API_AVAILABLE(ios(15.4))
@interface ClusterSceneDelegate : UIResponder <CPTemplateApplicationInstrumentClusterSceneDelegate>
@property (nonatomic, strong) NSString *clusterId;
- (instancetype)init;
@endClusterScene.m
//
// ClusterScene.m
// ABRP
//
// Created by Manuel Auer on 07.11.24.
//
#import "ClusterScene.h"
@implementation ClusterSceneDelegate
- (instancetype)init {
self = [super init];
if (self) {
self.clusterId = [[NSUUID UUID] UUIDString];
}
return self;
}
- (void)templateApplicationInstrumentClusterScene:
(CPTemplateApplicationInstrumentClusterScene *)
templateApplicationInstrumentClusterScene
didConnectInstrumentClusterController:
(CPInstrumentClusterController *)instrumentClusterController {
UIUserInterfaceStyle contentStyle = templateApplicationInstrumentClusterScene.contentStyle;
[RNCarPlay connectWithInstrumentClusterController:instrumentClusterController contentStyle:contentStyle clusterId:self.clusterId];
}
- (void)templateApplicationInstrumentClusterScene:(CPTemplateApplicationInstrumentClusterScene *)templateApplicationInstrumentClusterScene didDisconnectInstrumentClusterController:(CPInstrumentClusterController *)instrumentClusterController {
[RNCarPlay disconnectFromInstrumentClusterController:self.clusterId];
}
- (void)contentStyleDidChange:(UIUserInterfaceStyle)contentStyle {
[RNCarPlay clusterContentStyleDidChange:contentStyle clusterId:self.clusterId];
}
- (void)sceneDidEnterBackground:(UIScene *)scene {
[RNCarPlay clusterStateChanged:self.clusterId isVisible:false];
}
- (void)sceneWillEnterForeground:(UIScene *)scene {
[RNCarPlay clusterStateChanged:self.clusterId isVisible:true];
}
@endThis is what will run on the CarPlay Dashboard next to other widgets like Calendar, Music...
DashboardScene.h
//
// DashboardScene.h
// ABRP
//
// Created by Manuel Auer on 17.10.24.
//
#import <CarPlay/CarPlay.h>
#import <UIKit/UIKit.h>
#import "RNCarPlay.h"
@interface DashboardSceneDelegate : UIResponder <CPTemplateApplicationDashboardSceneDelegate>
@endDashboardScene.m
//
// DashboardScene.m
// ABRP
//
// Created by Manuel Auer on 17.10.24.
//
#import "DashboardScene.h"
@implementation DashboardSceneDelegate
- (void)templateApplicationDashboardScene:(CPTemplateApplicationDashboardScene *)templateApplicationDashboardScene
didConnectDashboardController:(CPDashboardController *)dashboardController
toWindow:(UIWindow *)window {
[RNCarPlay connectWithDashboardController:dashboardController window:window];
}
- (void)templateApplicationDashboardScene:(CPTemplateApplicationDashboardScene *)templateApplicationDashboardScene
didDisconnectDashboardController:(CPDashboardController *)dashboardController
fromWindow:(UIWindow *)window {
[RNCarPlay disconnectFromDashbaordController];
}
- (void)sceneDidEnterBackground:(UIScene *)scene {
[RNCarPlay dashboardStateChanged:false];
}
- (void)sceneWillEnterForeground:(UIScene *)scene {
[RNCarPlay dashboardStateChanged:true];
}
@endthis makes sure getInitialURL works as expected
RCTLinkingManager+Custom.mm
//
// RCTLinkingManager+Custom.mm
// this overrides the original RCTLinkingManager getInitialURL to provide the url the app was started with from a scene delegate
// Created by Manuel Auer on 04.10.24.
//
#import <React/RCTLinkingManager.h>
#import "LaunchURLStorage.h"
#import <React/RCTBridge.h>
#import <React/RCTUtils.h>
@implementation RCTLinkingManager (Custom)
RCT_EXPORT_METHOD(getInitialURL:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
{
NSURL *initialURL = [LaunchURLStorage shared].launchURL;
if (initialURL) {
resolve(RCTNullIfNil(initialURL.absoluteString));
return;
}
// Fallback to the original implementation
if (self.bridge.launchOptions[UIApplicationLaunchOptionsURLKey]) {
initialURL = self.bridge.launchOptions[UIApplicationLaunchOptionsURLKey];
} else {
NSDictionary *userActivityDictionary =
self.bridge.launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey];
if ([userActivityDictionary[UIApplicationLaunchOptionsUserActivityTypeKey] isEqual:NSUserActivityTypeBrowsingWeb]) {
initialURL = ((NSUserActivity *)userActivityDictionary[@"UIApplicationLaunchOptionsUserActivityKey"]).webpageURL;
}
}
resolve(RCTNullIfNil(initialURL.absoluteString));
}
@endrequired for the above RCTLinkingManager patch
LaunchURLStorage.h
//
// LaunchURLStorage.h
// ABRP
//
// Created by Manuel Auer on 04.10.24.
//
#import <Foundation/Foundation.h>
@interface LaunchURLStorage : NSObject
@property (nonatomic, strong) NSURL *launchURL;
+ (instancetype)shared;
@endLaunchURLStorage.m
//
// LaunchURLStorage.m
// ABRP
//
// Created by Manuel Auer on 04.10.24.
//
#import "LaunchURLStorage.h"
@implementation LaunchURLStorage
+ (instancetype)shared {
static LaunchURLStorage *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
@endadjust to your needs, this is the fully featured CarPlay, Dashboard & Cluster screen setup.
ios/App/Info.plist
<key>UIApplicationSceneManifest</key>
<dict>
<key>CPSupportsDashboardNavigationScene</key>
<true/>
<key>CPSupportsInstrumentClusterNavigationScene</key>
<true/>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
<key>UISceneConfigurations</key>
<dict>
<key>CPTemplateApplicationDashboardSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>CPTemplateApplicationDashboardScene</string>
<key>UISceneConfigurationName</key>
<string>Dashboard</string>
<key>UISceneDelegateClassName</key>
<string>DashboardSceneDelegate</string>
</dict>
</array>
<key>CPTemplateApplicationInstrumentClusterSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>CPTemplateApplicationInstrumentClusterScene</string>
<key>UISceneConfigurationName</key>
<string>Cluster</string>
<key>UISceneDelegateClassName</key>
<string>ClusterSceneDelegate</string>
</dict>
</array>
<key>CPTemplateApplicationSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>CPTemplateApplicationScene</string>
<key>UISceneConfigurationName</key>
<string>CarPlay</string>
<key>UISceneDelegateClassName</key>
<string>CarSceneDelegate</string>
</dict>
</array>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>UIWindowScene</string>
<key>UISceneConfigurationName</key>
<string>Phone</string>
<key>UISceneDelegateClassName</key>
<string>PhoneSceneDelegate</string>
</dict>
</array>
</dict>
</dict>| List | Grid | T B | Alert | A S | 🎤 | Map | 🔎 | POI | Info | 📇 | ||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| com.apple.developer.carplay-audio | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| com.apple.developer.carplay-communication | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
| com.apple.developer.carplay-charging | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ |
| com.apple.developer.carplay-maps | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ✅ | ✅ |
| com.apple.developer.carplay-parking | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ |
| com.apple.developer.carplay-quick-ordering | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ |
Efficiently manage CarPlay connections by utilizing the connected status and on-connect/disconnect events. Ensure to check the connection state before invoking CarPlay APIs, ideally within a useEffect hook or by using a non-React function.
// react
useEffect(() => {
CarPlay.registerOnConnect(onConnect);
return () => {
CarPlay.unregisterOnConnect(onConnect);
};
});
// imperative
CarPlay.registerOnConnect(() => {
CarPlay.setRootTemplate(/* template */);
});Templates are used to render contents on the CarPlay screen from your app. Details of the templates supported by apple can be found in the developer guide
A template that displays a navigation overlay that your app draws on the map.
new MapTemplate({
component: /* react native view */ MapView,
guidanceBackgroundColor: '#eeff00',
onAlertActionPressed() {},
onStartedTrip() {},
});See more configuration options in the TypeScript Docs
- CPMapTemplate: Learn more about the capabilities and limitations of the MapTemplate in CarPlay.
- CPMapTemplateDelegate: Understand the delegate callbacks that can be used to manage user interactions with the MapTemplate.
A template that displays and manages a list of items.
new ListTemplate({
sections: [
{
header: 'Header A',
items: [
{
text: 'Item 1',
},
],
},
],
title: 'List Template',
async onItemSelect() {},
});See more configuration options in the TypeScript Docs
- CPListTemplate: Learn more about the capabilities and limitations of the ListTemplate in CarPlay.
- CPListTemplateDelegate: Understand the delegate callbacks that can be used to manage user interactions with the ListTemplate.
A template that provides information for a point of interest, food order, parking location, or charging location.
new InformationTemplate({
title: 'Information',
items: [{ title: 'foo', detail: 'bar' }],
actions: [{ id: 'demo', title: 'Demo' }],
onActionButtonPressed() {},
});See more configuration options in the TypeScript Docs
- CPInformationTemplate: Learn more about the capabilities and limitations of the InformationTemplate in CarPlay.
- CPInformationTemplateDelegate: Understand the delegate callbacks that can be used to manage user interactions with the InformationTemplate.
A template that displays and manages a grid of items.
new GridTemplate({
trailingNavigationBarButtons: [
{
id: 'a',
type: 'image',
image: require('star.jpg'),
},
],
buttons: [
{
id: '0',
titleVariants: ['Item 0'],
image: require('click.jpg'),
},
],
title: 'Grid Template',
onButtonPressed() {},
onBarButtonPressed() {},
});See more configuration options in the TypeScript Docs
- CPGridTemplate: Discover the capabilities and constraints of the CPGridTemplate in CarPlay.
- CPGridTemplateDelegate: Delve into the delegate callbacks available for handling user interactions within the CPGridTemplate.
A template that provides the ability to search for a destination and see a list of search results.
new SearchTemplate({
async onSearch(query) {},
async onItemSelect({ index }) {},
onSearchButtonPressed() {},
});See more configuration options in the TypeScript Docs
- CPSearchTemplate: Explore the features and limitations of the CPSearchTemplate in CarPlay.
- CPSearchTemplateDelegate: Learn about the delegate callbacks for managing interactions within the CPSearchTemplate.
A template that displays a voice control indicator during audio input.
This template is presented via CarPlay.presentTemplate. In order to implement voice recognition, take a look at the @react-native-voice/voice package.
new VoiceControlTemplate({
voiceControlStates: [
{
identifier: 'a',
image: require('cat.jpg'),
repeats: true,
titleVariants: ['Searching...'],
},
],
});See more configuration options in the TypeScript Docs
- CPVoiceControlTemplate: Investigate the functionalities and boundaries of the CPVoiceControlTemplate in CarPlay.
- CPVoiceControlTemplateDelegate: Gain insights into the delegate methods designed to handle user voice commands in the CPVoiceControlTemplate.
- SFSpeechRecognizer: Learn about speech recognition tasks and integrations with CarPlay applications.
A template that displays a modal alert and should be presented via CarPlay.presentTemplate.
new AlertTemplate({
titleVariants: ['Hello world'],
actions: [
{
id: 'ok',
title: 'Ok',
},
{
id: 'ok',
title: 'Cancel',
},
{
id: 'remove',
title: 'Remove',
style: 'destructive',
},
],
onActionButtonPressed() {},
});- CPAlertTemplate: Explore the functionality of CPAlertTemplate for displaying alerts in CarPlay.
- CPAlertTemplateDelegate: Learn about delegate methods for managing user interactions with CarPlay alerts.
A template that displays a modal action sheet and should be presented via CarPlay.presentTemplate.
new ActionSheetTemplate({
title: 'Example',
message: 'This is an message for you',
actions: [
{
id: 'ok',
title: 'Ok',
},
{
id: 'remove',
title: 'Remove',
style: 'destructive',
},
],
onActionButtonPressed() {},
});- CPActionSheetTemplate: Discover how to present an action sheet in CarPlay using the CPActionSheetTemplate.
- CPActionSheetTemplateDelegate: Delve into the delegate methods to manage selections and handle user actions within a CPActionSheetTemplate.
A container template that displays and manages other templates, presenting them as tabs.
Note: This template must be set as the root template and cannot be pushed on top of other templates.
// Define tab templates
const tpl1 = new ListTemplate(/* ... */);
const tpl2 = new ListTemplate(/* ... */);
// Setup the tab container template
new TabBarTemplate({
templates: [tpl1, tpl2],
onTemplateSelect() {},
});- CPTabBarTemplate: Investigate the features and usage of CPTabBarTemplate to create a tab bar interface in CarPlay.
- CPTabBarTemplateDelegate: Explore the delegate methods for responding to tab selection events in the CPTabBarTemplate. ``
Quirks observed where PNG image resolutions should be specfied with scale factor of 3.0 (i.e. append with @3x) with ListTemplate image sizing suggested around 80 x 80 px per Issue #6









