Apache Cordova Guide — Build Mobile Apps with HTML, CSS, and JS
Apache Cordova wraps your web application in a native WebView, exposing device APIs through a unified JavaScript interface so you can build cross-platform mobile apps using the web technologies you already know.
What You’ll Learn
You’ll use the Cordova CLI to scaffold projects and add platforms, configure apps with config.xml, access native features through plugins (camera, geolocation, file), understand hybrid vs native tradeoffs, and build a complete app deployable to iOS and Android.
Why Cordova Matters
Cordova was the original hybrid mobile framework and is still used by thousands of production apps. It lets web developers target mobile platforms without learning Java, Kotlin, Swift, or Objective-C. Doda Browser’s companion survey app uses Cordova because it needs camera access on both platforms but doesn’t require native performance — development speed and code reuse matter more.
Cordova Learning Path
flowchart LR
A[HTML, CSS, JavaScript] --> B[Cordova CLI]
B --> C[config.xml]
C --> D[Platform Add]
D --> E[Plugins]
E --> F[Native APIs]
F --> G[Build & Deploy]
B:::current
classDef current fill:#E8E8E8,color:#000,stroke:#333,stroke-width:2px
How Cordova Works
Cordova creates a native wrapper — a thin native app shell containing a full-screen WebView. Your HTML, CSS, and JavaScript run inside this WebView like a web page, but with access to native device capabilities through plugins:
graph TD
A[Your Web App] --> B[Cordova JavaScript API]
B --> C[Plugin Layer]
C --> D[Native APIs]
D --> E[Camera]
D --> F[GPS]
D --> G[File System]
D --> H[Contacts]
D --> I[Accelerometer]
The JavaScript API communicates with native code through a bridge. When you call navigator.camera.getPicture(), Cordova marshals the request to native Java (Android) or Objective-C (iOS), captures the photo, and returns the result to JavaScript.
Getting Started with Cordova CLI
# Install Cordova globally
npm install -g cordova
# Create a new project
cordova create dodatech-app com.dodatech.app "DodaTech App"
cd dodatech-app
# Add platforms
cordova platform add android
cordova platform add ios # macOS only
# Check platform requirements
cordova requirementsThe create command generates this structure:
dodatech-app/
├── www/
│ ├── index.html # Entry point
│ ├── css/
│ ├── js/
│ └── img/
├── config.xml # App configuration
├── platforms/ # Platform-specific code
├── plugins/ # Installed plugins
├── hooks/ # Build lifecycle hooks
└── package.jsonConfiguring with config.xml
The config.xml file is the heart of your Cordova app — it controls metadata, preferences, and permissions:
<?xml version='1.0' encoding='utf-8'?>
<widget id="com.dodatech.app"
version="1.0.0"
xmlns="http://www.w3.org/ns/widgets"
xmlns:cdv="http://cordova.apache.org/ns/1.0">
<name>DodaTech App</name>
<description>A hybrid mobile app built with Apache Cordova</description>
<author email="dev@dodatech.com" href="https://dodatech.com">
DodaTech
</author>
<content src="index.html" />
<!-- Splash screen preferences -->
<preference name="SplashScreen" value="screen" />
<preference name="SplashScreenDelay" value="3000" />
<preference name="AutoHideSplashScreen" value="true" />
<!-- Status bar -->
<preference name="StatusBarOverlaysWebView" value="false" />
<preference name="StatusBarBackgroundColor" value="#2c3e50" />
<!-- Android-specific -->
<platform name="android">
<preference name="AndroidMinSdkVersion" value="24" />
<preference name="AndroidTargetSdkVersion" value="33" />
<preference name="orientation" value="portrait" />
</platform>
<!-- iOS-specific -->
<platform name="ios">
<preference name="TargetDevice" value="iPhone,iPad" />
<preference name="orientation" value="all" />
</platform>
<!-- Permissions -->
<edit-config file="AndroidManifest.xml" target="/manifest/application" mode="merge">
<application android:usesCleartextTraffic="true" />
</edit-config>
</widget>Using Plugins
Plugins are the bridge between JavaScript and native APIs. Install them with the CLI:
cordova plugin add cordova-plugin-camera
cordova plugin add cordova-plugin-geolocation
cordova plugin add cordova-plugin-file
cordova plugin add cordova-plugin-contactsCamera Plugin Example
// Capture a photo
function capturePhoto() {
navigator.camera.getPicture(
function(imageData) {
// Success callback — display the photo
const img = document.getElementById('photo');
img.src = 'data:image/jpeg;base64,' + imageData;
img.style.display = 'block';
},
function(error) {
// Error callback
console.error('Camera error:', error);
alert('Failed to capture photo: ' + error);
},
{
quality: 80,
destinationType: Camera.DestinationType.DATA_URL,
sourceType: Camera.PictureSourceType.CAMERA,
encodingType: Camera.EncodingType.JPEG,
saveToPhotoAlbum: true
}
);
}
// Select from gallery
function selectFromGallery() {
navigator.camera.getPicture(onSuccess, onFail, {
quality: 80,
destinationType: Camera.DestinationType.FILE_URI,
sourceType: Camera.PictureSourceType.PHOTOLIBRARY
});
}Output: On Android or iOS, tapping “Capture Photo” opens the native camera app. After capturing, the photo appears in the <img> element as a base64-encoded data URL. Error handling shows an alert if the camera isn’t available.
Geolocation Plugin Example
function getCurrentPosition() {
navigator.geolocation.getCurrentPosition(
function(position) {
const lat = position.coords.latitude;
const lng = position.coords.longitude;
const accuracy = position.coords.accuracy;
document.getElementById('location').innerHTML = `
<p>Latitude: ${lat.toFixed(6)}</p>
<p>Longitude: ${lng.toFixed(6)}</p>
<p>Accuracy: ${accuracy.toFixed(1)} meters</p>
`;
// Show on map
showOnMap(lat, lng);
},
function(error) {
console.error('Geolocation error:', error);
switch(error.code) {
case error.PERMISSION_DENIED:
alert('Location permission denied');
break;
case error.POSITION_UNAVAILABLE:
alert('Location unavailable');
break;
case error.TIMEOUT:
alert('Location request timed out');
break;
}
},
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 60000
}
);
}Output: The native GPS or network location provider determines the device’s position. Coordinates and accuracy are displayed. Error handling catches permission denials, timeouts, and unavailable signals — common in indoor environments.
File System Plugin Example
function saveNoteToFile(content, filename) {
// Request persistent storage
window.requestFileSystem(
LocalFileSystem.PERSISTENT,
0,
function(fileSystem) {
// Create or open the file
fileSystem.root.getFile(
filename,
{ create: true, exclusive: false },
function(fileEntry) {
// Write content
fileEntry.createWriter(function(writer) {
writer.onwriteend = function() {
console.log('File saved to:', fileEntry.toURL());
alert('Note saved successfully!');
};
writer.onerror = function(e) {
console.error('Write failed:', e.toString());
};
writer.write(content);
});
},
function(error) {
console.error('File error:', error.code);
}
);
},
function(error) {
console.error('File system error:', error.code);
}
);
}
function listSavedFiles() {
window.requestFileSystem(
LocalFileSystem.PERSISTENT,
0,
function(fileSystem) {
const dirReader = fileSystem.root.createReader();
dirReader.readEntries(
function(entries) {
const list = document.getElementById('file-list');
list.innerHTML = '';
entries.forEach(function(entry) {
if (entry.isFile) {
const li = document.createElement('li');
li.textContent = entry.name + ' (' + entry.size + ' bytes)';
list.appendChild(li);
}
});
},
function(error) {
console.error('Read error:', error.code);
}
);
}
);
}Output: The app saves text content to persistent device storage. On subsequent launches, it lists all saved files with their sizes. The file persists across app restarts and is accessible only to the app (sandboxed storage).
Hybrid vs Native Tradeoffs
| Aspect | Hybrid (Cordova) | Native |
|---|---|---|
| Development speed | Fast — one codebase | Slower — per-platform |
| Performance | WebView-bound | Full native speed |
| UI feel | Web-like, slight lag | Platform-native feel |
| API access | Plugin-dependent | Full SDK access |
| App size | Larger (WebView engine) | Smaller per-platform |
| Maintenance | Single codebase | Multiple codebases |
Cordova is ideal for: internal enterprise apps, prototypes, content-focused apps, and teams with web expertise but limited mobile experience.
Security Angle: Secure Plugin Usage
// Validate file paths to prevent directory traversal
function safeReadFile(userProvidedPath) {
// Sanitize the path — remove any ".." components
const sanitized = userProvidedPath
.replace(/\.\.\//g, '') // Remove parent directory references
.replace(/\.\.\\/g, '') // Windows variant
.replace(/~/g, ''); // Remove home directory references
// Ensure path starts with expected directory
if (!sanitized.startsWith('/notes/')) {
console.error('Invalid path — must be in /notes/ directory');
return;
}
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function(fs) {
fs.root.getFile(sanitized, { create: false },
function(entry) {
// Read and display file
},
function(error) {
console.error('File access denied');
}
);
});
}DodaTech’s mobile security scanner uses these same validation patterns to prevent path traversal attacks when scanning user-specified file locations.
Common Mistakes Beginners Make
Forgetting
devicereadyevent: Cordova plugins are not available until thedevicereadyevent fires. Accessing plugins before this event causes undefined errors. Always wrap your app initialization in this event listener.Testing in browser without plugins: Running
cordova serveor openingindex.htmldirectly in a desktop browser won’t load native plugins. Always test on real devices or emulators.Not handling permission denials: Android 6+ and iOS require runtime permission requests. Plugins fail gracefully, but your app must handle the error callbacks and prompt users to grant permissions.
Blocking the UI thread: Heavy JavaScript operations freeze the WebView. Use Web Workers or chunk operations with
requestAnimationFramefor smooth scrolling.Not specifying Content Security Policy (CSP): Cordova apps are vulnerable to XSS without CSP headers. Add
<meta http-equiv="Content-Security-Policy">in yourindex.html.Platform-specific CSS issues: Android WebView and iOS WKWebView render CSS differently. Test both platforms and use CSS resets or framework normalization.
Practice Questions
- What event must fire before using Cordova plugins?
- How do you add the camera plugin to a Cordova project?
- What is the difference between
DATA_URLandFILE_URIfor camera images? - Why might you choose Cordova over React Native?
- What is the purpose of
config.xml?
Answers:
- The
devicereadyevent. Cordova fires this event when the native layer is initialized and plugins are available. - Run
cordova plugin add cordova-plugin-camerain the project directory. The plugin is downloaded from npm and configured for all added platforms. DATA_URLreturns the image as a base64-encoded string (memory-intensive for large images).FILE_URIreturns a file path to the saved image (more memory-efficient).- Cordova uses standard web technologies (HTML, CSS, JavaScript) with no framework dependency, ideal for teams with web expertise who want to reuse existing code.
- It configures app metadata, permissions, splash screen, orientation, and platform-specific preferences. It’s equivalent to
AndroidManifest.xml+Info.plistcombined.
Challenge
Build a field inspection app: use the camera plugin to capture photos of equipment, the geolocation plugin to tag inspection location, the file plugin to save inspection reports as JSON files, and a list view to browse past inspections.
Real-World Task
Create a customer feedback app: collect user feedback with a form (name, rating, comments), attach photos using the camera plugin, save submissions to local storage with the file plugin, and export all feedback as a CSV file for analysis.
FAQ
Try It Yourself
cordova create dodatech-camera-app com.dodatech.camera "Camera Demo"
cd dodatech-camera-app
cordova platform add android
cordova plugin add cordova-plugin-cameraEdit www/js/index.js to add a “Take Photo” button that captures a photo using the camera plugin and displays it. Build and run on a device.
What’s Next
Related topics: JavaScript, HTML, CSS, Ionic
What’s Next
Congratulations on completing this Apache Cordova tutorial! Here’s where to go from here:
- Practice daily — Consistency is more important than long study sessions
- Build a project — Apply what you learned by building something real
- Explore related topics — Check out other tutorials in the same category
- Join the community — Discuss with other learners and share your progress
Remember: every expert was once a beginner. Keep coding!
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro