Create Free APK

JavaScript bridge API

Capacitor plugins

The Capacitor plugins the app hosts. A hand-written page reaches them as window.Capacitor.Plugins.<Name>; an AI-built app uses the @/lib/appmintNative wrapper each example names.

Capacitor.ActionSheet Capacitor#

Capacitor.Plugins.ActionSheet — showActions({ title, message, options })

Example

Shows a native bottom sheet with a list of choices and tells you which one was tapped (@capacitor/action-sheet 8.1.1).

Returns: Promise { index, canceled } - index is the tapped option; when the sheet is dismissed canceled is true and index is outside the list (-1). Needs: nothing to switch on - every app hosts the plugin. window.Capacitor exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A Website mode app loads a remote site, and that site never gets window.Capacitor - feature-detect.

Each option is { title, style? } with style 'DEFAULT', 'DESTRUCTIVE' or 'CANCEL'.

var ActionSheet = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.ActionSheet;

async function photoMenu() {
  if (!ActionSheet) return;
  var opts = [
    { title: 'Share', style: 'DEFAULT' },
    { title: 'Edit', style: 'DEFAULT' },
    { title: 'Delete', style: 'DESTRUCTIVE' }
  ];
  var r = await ActionSheet.showActions({ title: 'Photo', message: 'Choose an action', options: opts });
  if (r.canceled || r.index < 0 || r.index >= opts.length) return;   // dismissed
  if (opts[r.index].title === 'Delete') deletePhoto();
}

In an AI-built app the Capacitor-shaped wrapper returns { index: -1 } on dismiss; the short form actionSheet() returns null:

import { ActionSheet, actionSheet } from '@/lib/appmintNative';

const { index } = await ActionSheet.showActions({
  title: 'Photo',
  options: [{ title: 'Share' }, { title: 'Delete', destructive: true }],
});
if (index === 1) deletePhoto();

const i = await actionSheet('Sort by', [{ title: 'Name' }, { title: 'Date' }]);   // number | null

Notes: Always check the index bound - never treat any index as a real choice. The wrapper's option shape is { title, destructive? }; it maps destructive to the plugin's 'DESTRUCTIVE' style.

Capacitor.App Capacitor#

Capacitor.Plugins.App — getInfo(), getState(), exitApp(), minimizeApp(), addListener(...)

Example

App identity and lifecycle: name, package id, version, whether the app is in front, and pause/resume events (@capacitor/app 8.1.0).

Returns: getInfo(){ name, id, build, version }; getState(){ isActive }; getLaunchUrl(){ url } or nothing; getAppLanguage(){ value }; exitApp() / minimizeApp()void. addListener() → Promise of a handle with remove(). Needs: nothing to switch on - every app hosts the plugin. window.Capacitor exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A Website mode app loads a remote site, and that site never gets window.Capacitor - feature-detect.

var App = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.App;
if (App) {
  App.getInfo().then(function (i) {
    document.getElementById('about').textContent = i.name + ' ' + i.version + ' (' + i.build + ')';
  });
}

Pause and resume - save a draft when the user leaves, refresh when they come back:

if (App) {
  App.addListener('appStateChange', function (s) {
    if (!s.isActive) saveDraft();      // went to the background
    else refreshInbox();               // back in front
  });
  App.addListener('pause', function () { console.log('paused'); });
  App.addListener('resume', function () { console.log('resumed'); });
}

In an AI-built app:

import { App } from '@/lib/appmintNative';

const info = await App.getInfo();   // { name, id, build, version }
App.exitApp();                      // closes the app on Android

Notes: The backButton event never fires: the shell keeps the back button (the plugin runs with disableBackButtonHandler). Handle back with AppMint.setBackHandler(fn) or the onBackGesture wrapper. For deep links use WebToApk.getLaunchUrl() / the appmint:deep-link event (or launchUrl() / onDeepLink() in AI-built apps). minimizeApp() is Android-only. Never make an Exit button the only way forward.

Capacitor.AppLauncher Capacitor#

Capacitor.Plugins.AppLauncher — canOpenUrl({ url }), openUrl({ url })

Example

Opens another app by link (whatsapp://send?text=…, tel:, geo:, an https link) and asks whether one can handle it (@capacitor/app-launcher 8.0.1).

Returns: canOpenUrl() → Promise { value: boolean }. openUrl() → Promise { completed: boolean } (false when no app took the link). Needs: nothing to switch on - every app hosts the plugin. window.Capacitor exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A Website mode app loads a remote site, and that site never gets window.Capacitor - feature-detect.

var AL = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppLauncher;

async function chatOnWhatsApp(text) {
  if (!AL) return;
  var url = 'whatsapp://send?text=' + encodeURIComponent(text);
  var can = await AL.canOpenUrl({ url: url });
  if (!can.value) { alert('WhatsApp is not installed'); return; }
  var r = await AL.openUrl({ url: url });
  if (!r.completed) alert('Could not open WhatsApp');
}

In an AI-built app:

import { appLauncher } from '@/lib/appmintNative';

if (await appLauncher.canOpen('tel:+15551234567')) await appLauncher.open('tel:+15551234567');

Notes: On Android 11+ an app can only "see" other apps it declares. Generated apps declare https, geo, google.navigation, tel, mailto, sms, whatsapp, intent and upi links, so canOpenUrl answers false for other schemes (for example spotify:) even when the app is installed. openUrl still works for them - call it and check completed. On Android canOpenUrl also accepts a package name ('com.whatsapp'), with the same visibility limit.

Capacitor.AppUpdate Capacitor#

Capacitor.Plugins.AppUpdate — getAppUpdateInfo(), performImmediateUpdate(), startFlexibleUpdate(), completeFlexibleUpdate(), openAppStore()

Example

Checks Google Play for a newer version of the app and starts Play's in-app update. Vendored @capawesome/capacitor-app-update (template/plugins/app-update).

Returns: getAppUpdateInfo(){ currentVersionName, currentVersionCode, availableVersionCode, updateAvailability, updatePriority, immediateUpdateAllowed, flexibleUpdateAllowed, clientVersionStalenessDays?, installStatus } (version codes are strings; updateAvailability 2 = an update is available). performImmediateUpdate() / startFlexibleUpdate(){ code }: 0 OK, 1 cancelled, 2 failed, 3 not available, 4 not allowed, 5 call getAppUpdateInfo() first. openAppStore()void. Needs: nothing to switch on; the app must be installed from Google Play. window.Capacitor exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A Website mode app loads a remote site, and that site never gets window.Capacitor - feature-detect.

var AU = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppUpdate;

async function checkForUpdate() {
  if (!AU) return;
  try {
    var info = await AU.getAppUpdateInfo();          // must come first
    if (info.updateAvailability !== 2) return;       // up to date
    if (info.immediateUpdateAllowed) {
      var r = await AU.performImmediateUpdate();     // Play's full-screen update
      if (r.code === 1) console.log('user said not now');
    } else {
      await AU.openAppStore();                       // the Play listing
    }
  } catch (e) {
    console.log('Play update check failed:', e.message);  // e.g. no Play services
  }
}

Flexible update - download in the background, then restart when the user agrees:

async function flexibleUpdate() {
  if (!AU) return;
  var info = await AU.getAppUpdateInfo();
  if (info.updateAvailability !== 2 || !info.flexibleUpdateAllowed) return;
  await AU.addListener('onFlexibleUpdateStateChange', function (s) {
    if (s.installStatus === 2) console.log(s.bytesDownloaded + ' / ' + s.totalBytesToDownload);
    if (s.installStatus === 11) showRestartButton();   // 11 = DOWNLOADED
  });
  await AU.startFlexibleUpdate();
}
function restartNow() { if (AU) AU.completeFlexibleUpdate(); }

In an AI-built app:

import { appUpdate } from '@/lib/appmintNative';

const info = await appUpdate.check();          // null on the web / without Play
if (info?.inPlace) await appUpdate.start();    // Android: immediate update

Notes: Use updateAvailability === 2 as the only "update available" test. The wrapper's check() compares currentVersionName with availableVersionCode, which are never equal on Android, so its updateAvailable is not reliable there - test inPlace instead. start() resolves true even when the user cancels.

Capacitor.AppwrightDocScanner Capacitor#

Capacitor.Plugins.AppwrightDocScanner — scan({ pageLimit, pdf, quality }), isSupported()

Example

Opens the Google ML Kit document scanner: the user photographs one or more pages (or imports them), the edges are found and straightened, and the pages come back as JPEG images. Appwright's own plugin (AppwrightDocScannerPlugin.kt).

Returns: scan() → Promise that always RESOLVES: { ok: true, pages: string[] (JPEG data URLs), pdfDataUrl?, pdfPath?, pdfError? } on success, { ok: false, cancelled: true } when the user backs out, { ok: false, error } when the scanner cannot start. isSupported(){ value: boolean } (Google Play services present). Needs: nothing to switch on and no camera permission (the scanner runs inside Play services, which downloads its model on first use).

var Scanner = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppwrightDocScanner;

async function scanReceipt() {
  if (!Scanner) { alert('Scanning is not available here'); return; }
  var s = await Scanner.isSupported();
  if (!s.value) { alert('This phone has no Google Play services'); return; }
  var r = await Scanner.scan({ pageLimit: 1, pdf: false, quality: 0.8 });
  if (r.cancelled) return;
  if (!r.ok) { alert('Scanner error: ' + r.error); return; }
  document.getElementById('preview').src = r.pages[0];
}

pageLimit 0 = no limit. quality is 0.1-1 for the JPEG. With pdf: true the scanner also writes a PDF: pdfDataUrl is it as a data:application/pdf;base64,… URL the page can download, upload or show, and pdfPath is its copy in the app's cache folder. If the PDF could not be read, the pages still return and pdfError says why.

In an AI-built app (null on cancel, error, or the web):

import { docScanner, can } from '@/lib/appmintNative';

if (can('docScanner') !== 'none') {
  const res = await docScanner.scan({ pageLimit: 5, pdf: true });
  if (res) setPages(res.pages);          // res.pdfPath when pdf: true
}

Notes: Pages are full data URLs and can be large - shrink them before saving many in localStorage. Use pdfDataUrl, not pdfPath - a device file path is not a URL the page can load. A page that cannot be read is skipped; the rest still return.

Capacitor.AppwrightGallery Capacitor#

Capacitor.Plugins.AppwrightGallery — save({ source, fileName, album, kind })

Example

Saves a photo or a video into the phone's gallery (Pictures/<album> or Movies/<album>). The app shell's own plugin (AppwrightGalleryPlugin.kt).

Returns: Promise of { uri } of the saved item. Rejects with a code: bad-source, download-failed, permission-denied (Android 7-9, storage refused) or failed. Needs: nothing to switch on; no permission on Android 10+ (Android 7-9 asks for storage the first time).

const Gallery = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppwrightGallery;

async function saveCard(canvas) {
  const dataUrl = canvas.toDataURL('image/png');
  if (!Gallery) { const a = document.createElement('a'); a.href = dataUrl; a.download = 'card.png'; a.click(); return; }
  try {
    await Gallery.save({ source: dataUrl, fileName: 'birthday-card.png', album: 'Cards' });
    toast('Saved to your gallery');
  } catch (e) {
    toast('Could not save: ' + e.message);
  }
}

Notes: source is a data: URL, an http(s) URL or a content:///file:// URI; kind ('image'/'video') is read from the MIME type when omitted. AI-built apps use saveToGallery() from @/lib/appmintNative.

Capacitor.AppwrightHaptics Capacitor#

Capacitor.Plugins.AppwrightHaptics

Example

AppMint's own plugin for rich vibration patterns: a list of beats, each with its own start time, length and strength.

Returns: play({ events }) resolves { ok: true } or { ok: false, error } ('unsupported' = no vibrator, 'empty' = no events, or the system's message). It never rejects. isSupported() resolves { value: true/false }. Needs: nothing to switch on - every app declares the vibration permission, whether or not Vibrate is ticked in Step 4 (Access). window.Capacitor exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A Website mode app loads a remote site, and that site never gets window.Capacitor - feature-detect.

Each event is { at, duration, intensity, sharpness }: at = start in ms, duration = length in ms (under 20 becomes a 30 ms tap), intensity = 0-1 (default 0.8). sharpness is accepted but ignored on Android.

var AH = window.Capacitor && window.Capacitor.Plugins ? window.Capacitor.Plugins.AppwrightHaptics : null;

async function heartbeat() {
  if (!AH) return false;
  var r = await AH.play({
    events: [
      { at: 0,   intensity: 0.8 },
      { at: 140, intensity: 0.5 },
      { at: 700, intensity: 0.8 },
      { at: 840, intensity: 0.5 }
    ]
  });
  if (!r.ok) console.log('pattern not played:', r.error);
  return r.ok;
}

document.getElementById('like').addEventListener('click', heartbeat);

Hide the button on phones with no vibrator:

(async function () {
  var btn = document.getElementById('feel-it');
  var ok = false;
  if (AH) { try { ok = (await AH.isSupported()).value; } catch (e) {} }
  btn.hidden = !ok;
})();

Notes: Strength works only on phones with amplitude control; other phones keep the timing at one fixed strength. If the system refuses the vibration, play answers { ok: false, error: '<the system message>' }. In AI-built apps use hapticPlay from @/lib/appmintNative, which also has named patterns.

Capacitor.AppwrightLiveProgress Capacitor#

Capacitor.Plugins.AppwrightLiveProgress — start / update / end

Example

AppMint's own Android plugin (its JS name is AppwrightLiveProgress) for a sticky, silent, live-updating status notification (progress bar, spinner or running clock) that opens a route when tapped.

Returns: start(opts) / update(opts)Promise<{ ok: true, id }>, or { ok: false, error } with nothing posted - 'permission' when notifications are not allowed (Android 13+: not granted yet, or switched off for the app), 'channel_disabled' when the user turned off the "Live status" category; end({ id })Promise<void>. Needs: the Android app and the notification permission (Android 13+). Not available on the web.

In an AI-built app use the wrapper liveProgress from @/lib/appmintNative:

import { liveProgress } from '@/lib/appmintNative';
await liveProgress.start({ id: 'order', title: 'Order #42', text: 'Being prepared', indeterminate: true });
await liveProgress.update({ id: 'order', title: 'Order #42', text: 'On the way', progress: 0.6, route: '/orders/42' });
await liveProgress.end('order');

In a hand-written page:

async function showDelivery(step, total) {
  const P = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppwrightLiveProgress;
  if (!P) return;                                   // not in the Android app
  if (window.Notification && Notification.permission !== 'granted') {
    if (await Notification.requestPermission() !== 'granted') return;
  }
  const r = await P.update({
    id: 'delivery',
    title: 'Your delivery',
    text: 'Stop ' + step + ' of ' + total,
    progress: step / total,                         // 0–1; or indeterminate: true
    route: '/track'                                 // opened when the card is tapped
  });
  if (!r.ok) console.warn('live card not posted:', r.error);
}

async function deliveryDone() {
  const P = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppwrightLiveProgress;
  if (P) await P.end({ id: 'delivery' });
}

Options: id (default 'live'), title, text, progress (0-1, omit for no bar), indeterminate, startedAt (epoch ms, shows a running clock), route.

Notes: start and update do the same thing (post or replace the card with that id). The card cannot be swiped away - always call end. Ask for the permission first: on Android 13+ without it start answers error: 'permission' and posts nothing.

Capacitor.AppwrightSystemPalette Capacitor#

Capacitor.Plugins.AppwrightSystemPalette — get()

Example

Returns the phone's Material You colours (the palette Android 12+ builds from the wallpaper), so the app can match the system apps. AppMint's own plugin (AppwrightSystemPalettePlugin.kt).

Returns: Promise { available: false } below Android 12, else { available: true, accent1, accent2, accent3, neutral1, neutral2 } - each an object from tone to hex colour, with tones '0', '10', '50', '100''900', '1000' (e.g. accent1['600'] === '#3F5F90'). Needs: nothing to switch on - every app hosts the plugin. window.Capacitor exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A Website mode app loads a remote site, and that site never gets window.Capacitor - feature-detect.

var Palette = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppwrightSystemPalette;

async function useWallpaperColours() {
  if (!Palette) return;
  var p = await Palette.get();
  if (!p.available) return;                 // keep the app's own colours
  var root = document.documentElement.style;
  root.setProperty('--accent', p.accent1['600']);
  root.setProperty('--surface', p.neutral1['50']);
}
useWallpaperColours();

In an AI-built app:

import { systemPalette } from '@/lib/appmintNative';

const p = await systemPalette();
if (p.available && p.accent1) document.documentElement.style.setProperty('--accent', p.accent1['600']);

Notes: Check text contrast before you apply palette colours - a wallpaper tone is not guaranteed to read well on your background. Browsers expose no wallpaper colours: on the web the wrapper answers { available: false }. The colours change when the user changes the wallpaper; call get() again on resume.

Capacitor.AppwrightWidget Capacitor#

Capacitor.Plugins.AppwrightWidget — set(card), clear(), placedCount(), requestPin()

Example

Fills the app's home-screen widget: one card with a title, a big value, up to six lines, your colours and a tap route. The card is saved on the phone and shown even after a reboot. AppMint's own plugin (AppwrightWidgetPlugin.kt).

Returns: set({ title?, value?, lines?, route?, bg?, ink?, accent? }) → Promise { ok: true, placed } (placed = widgets currently on the home screen). clear()void. placedCount(){ value }. requestPin(){ value: boolean } (true when the launcher's "add widget" sheet was opened). Needs: nothing to switch on - every app declares the widget. window.Capacitor exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A Website mode app loads a remote site, and that site never gets window.Capacitor - feature-detect.

var Widget = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppwrightWidget;

async function updateWidget(tasks) {
  if (!Widget) return;
  await Widget.set({
    title: 'Today',
    value: tasks.length + ' tasks',
    lines: tasks.slice(0, 6).map(function (t) { return t.title; }),
    route: '/today',                 // opened when the widget is tapped
    bg: '#0B0F1A', ink: '#FFFFFF', accent: '#22D3EE'
  });
}

Offer to place it (only when none is placed yet):

async function offerWidget() {
  if (!Widget) return;
  var n = (await Widget.placedCount()).value;
  if (n > 0) return;
  var r = await Widget.requestPin();
  if (!r.value) alert('Long-press the home screen, tap Widgets and pick this app.');
}

In an AI-built app:

import { widget } from '@/lib/appmintNative';

await widget.set({ title: 'Today', value: '3 tasks', lines: ['Gym', 'Call mum'], route: '/today' });
if ((await widget.placedCount()) === 0) await widget.requestPin();

Notes: Colours are hex strings; empty ones use a dark card with a cyan value. The tap opens the app at route as a deep link (read it with WebToApk.getLaunchUrl() or the appmint:deep-link event). Before any set() the widget says "Nothing to show yet"; clear() returns it to that state. In a browser can('widget') is 'none'.

Capacitor.Badge Capacitor#

Capacitor.Plugins.Badge — the launcher-icon count

Example

The Badge plugin (capawesome) that sets the number on the app's launcher icon.

Returns: Promises: set({ count }), clear(), increase(), decrease()void; get(){ count }; isSupported(){ isSupported }. The Android plugin has no permission methods. Needs: nothing on Android. Not available on the web.

In an AI-built app use the wrapper badge from @/lib/appmintNative:

import { badge } from '@/lib/appmintNative';
await badge.set(3);
await badge.clear();

In a hand-written page:

async function setUnread(count) {
  const B = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Badge;
  if (!B) return false;                       // not inside the app
  try {
    const s = await B.isSupported();
    if (!s.isSupported) return false;         // this launcher shows no badges
    if (count > 0) await B.set({ count: count });
    else await B.clear();
    return true;
  } catch (e) {
    return false;
  }
}

window.addEventListener('appmint:push', function () {
  const inbox = JSON.parse(window.WebToApk ? WebToApk.getPushInbox() : '[]');
  setUnread(inbox.filter(function (m) { return !m.read; }).length);
});

Notes: on Android the look depends on the launcher (a number, a dot, or nothing). navigator.setAppBadge(n) in the app writes to the same badge (it exists only on launchers that can show a count).

Capacitor.Browser Capacitor#

Capacitor.Plugins.Browser — open({ url, toolbarColor }), close(), addListener('browserFinished')

Example

Opens a web page in an in-app browser tab (Chrome Custom Tabs), so the user reads terms, docs or a sign-in page and comes back with one gesture (@capacitor/browser 8.0.3).

Returns: open() / close() → Promise void. Events: browserFinished (the user closed the tab) and browserPageLoaded. Needs: nothing - every app hosts the plugin (a browser that supports Custom Tabs, such as Chrome, must be installed). window.Capacitor exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A Website mode app loads a remote site, and that site never gets window.Capacitor - feature-detect.

var Browser = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Browser;

async function openTerms() {
  if (!Browser) return;
  var h = await Browser.addListener('browserFinished', function () {
    console.log('user closed the terms');
    h.remove();
  });
  await Browser.open({ url: 'https://example.com/terms', toolbarColor: '#0B0F1A' });
}

In an AI-built app (a new tab on the web):

import { browser } from '@/lib/appmintNative';

const off = browser.onClosed(() => refreshAccount());
await browser.open('https://example.com/terms', { toolbarColor: '#0B0F1A' });
// call off() when you no longer care

Notes: url must not be empty. toolbarColor is a hex colour; an invalid one is ignored. close() also works on Android in this plugin version (it finishes the tab's host activity) - useful after a sign-in link returns to the app. The tab is a separate browser: it does not share the app's cookies or localStorage.

Capacitor.Calendar Capacitor#

Capacitor.Plugins.Calendar — createEvent, findEvents, deleteEvent, listCalendars, openCalendar

Example

Adds, finds and deletes events in the phone's own calendar (Android CalendarContract), and opens the calendar app at a date. Vendored @capacitor/calendar (template/plugins/calendar).

Returns: createEvent(){ id }; findEvents(){ events: [{ id, title, location?, notes?, startDate, endDate, isAllDay, calendarId, calendarName?, attendees? }] }; listCalendars(){ calendars: [{ id, name, displayName?, isPrimary }] }; deleteEvent(), openCalendar()void. Dates are epoch milliseconds. Needs: tick Calendar in Step 4 (Access) when you build. For HTML, ZIP and AI-built apps the build also adds the calendar permission by itself when your files contain a Calendar.<method>( call written exactly so (as below) or the wrapper's calendar.createEvent(. The plugin asks the user for calendar access on first use. window.Capacitor exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A Website mode app loads a remote site, and that site never gets window.Capacitor - feature-detect.

var Calendar = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Calendar;

async function addDentist() {
  if (!Calendar) return;
  var start = new Date('2026-10-02T10:00:00').getTime();
  try {
    var r = await Calendar.createEvent({
      title: 'Dentist', startDate: start, endDate: start + 60 * 60 * 1000,
      location: 'Main St 5', notes: 'Bring the card', isAllDay: false,
      firstReminderMinutes: 30
    });
    console.log('event id', r.id);
  } catch (e) {
    // e.code: OS-PLUG-CLDR-0020 permission denied, -0001 bad arguments
    alert('Could not add the event: ' + e.message);
  }
}

Read this week, delete one, open the calendar:

async function thisWeek() {
  if (!Calendar) return;
  var now = Date.now();
  var found = await Calendar.findEvents({ startDate: now, endDate: now + 7 * 864e5 });
  found.events.forEach(function (ev) { console.log(ev.title, new Date(ev.startDate)); });
  if (found.events.length) await Calendar.deleteEvent({ id: found.events[0].id });
  await Calendar.openCalendar({ date: now });
}

In an AI-built app (on the web createEvent downloads an .ics file; reading returns []):

import { calendar } from '@/lib/appmintNative';

if ((await calendar.requestPermission()) === 'granted') {
  const id = await calendar.createEvent({ title: 'Dentist', start: new Date('2026-10-02T10:00'), end: new Date('2026-10-02T11:00') });
  const week = await calendar.listEvents({ from: Date.now(), to: Date.now() + 7 * 864e5 });
  if (id) await calendar.deleteEvent(id);
  await calendar.openCalendar(new Date());
}

Notes: Without the calendar permission in the app, no dialog appears and every event call rejects (missing permission); a user who says no gets OS-PLUG-CLDR-0020. recurrence: { frequency: 'daily' | 'weekly' | 'monthly' | 'yearly', interval?, count?, endDate? } makes a repeating event. createEventInteractively({ title, startDate, endDate }) opens the calendar app's own editor instead (no id comes back). The wrapper returns null / [] / false instead of throwing.

Capacitor.Camera Capacitor#

Capacitor.Plugins.Camera

Example

The official Capacitor Camera plugin, built into the app: take a photo with the camera or choose photos from the gallery.

Returns: Promises. getPhoto(opts){ dataUrl | base64String | webPath, format, saved } (which field depends on resultType), and REJECTS when the user cancels; pickImages(opts){ photos: [{ webPath, format }] }; checkPermissions() / requestPermissions({ permissions }){ camera, photos }. Needs: tick Camera in Step 4 (Access). For HTML, ZIP and AI-built apps the build also adds the camera permission by itself when your files contain Camera.getPhoto( written exactly so (as below), getUserMedia with video, or <input capture>. window.Capacitor exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A Website mode app loads a remote site, and that site never gets window.Capacitor - feature-detect.

Take a photo and show it.

async function takePhoto() {
  var Camera = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Camera;
  if (!Camera) { showMessage('Open the app to use the camera.'); return; }
  try {
    var perm = await Camera.requestPermissions({ permissions: ['camera'] });
    if (perm.camera !== 'granted') { showMessage('Camera permission was refused.'); return; }

    var photo = await Camera.getPhoto({
      source: 'CAMERA',          // 'CAMERA' | 'PHOTOS' | 'PROMPT'
      resultType: 'dataUrl',     // 'dataUrl' | 'base64' | 'uri'
      quality: 80,
      direction: 'REAR',         // 'REAR' | 'FRONT'
      correctOrientation: true,
      saveToGallery: false
    });
    document.getElementById('photo').src = photo.dataUrl;
  } catch (err) {
    // The user closed the camera (or no camera app). Nothing to do.
  }
}

Choose up to 5 photos from the gallery.

async function choosePhotos() {
  var Camera = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Camera;
  if (!Camera) return;
  try {
    var result = await Camera.pickImages({ quality: 80, limit: 5 });
    result.photos.forEach(function (p) {
      var img = document.createElement('img');
      img.src = p.webPath;       // a URL the page can load
      document.getElementById('gallery').appendChild(img);
    });
  } catch (err) { /* cancelled */ }
}

In an AI-built app use the wrapper, which also works on the web (and resolves null instead of throwing on cancel):

import { camera } from '@/lib/appmintNative';
const photo = await camera.take({ source: 'camera' });

Notes: pickImages needs no camera permission. If the camera permission is not in the app (the box was off and the build found no camera call), the camera permission is refused with no prompt. An <input type="file" accept="image/*" capture> also opens the camera. Camera is sensitive: declare it in your Play Data safety form.

Capacitor.Clipboard Capacitor#

Capacitor.Plugins.Clipboard — write(options), read()

Example

Copies text (or a link) to the system clipboard and reads it back (@capacitor/clipboard 8.0.1).

Returns: write({ string?, url?, image?, label? }) → Promise void. read() → Promise { value, type } (type is 'text/plain' for text). Needs: nothing to switch on - every app hosts the plugin. window.Capacitor exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A Website mode app loads a remote site, and that site never gets window.Capacitor - feature-detect.

async function copyCode(code) {
  var Clipboard = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Clipboard;
  if (!Clipboard) { alert('Copy is not available here'); return; }
  await Clipboard.write({ string: code, label: 'Invite code' });
}

async function pasteCode(input) {
  var Clipboard = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Clipboard;
  if (!Clipboard) return;
  try {
    var r = await Clipboard.read();
    input.value = r.value;
  } catch (e) {
    // An EMPTY clipboard is a rejection: "There is no data on the clipboard".
    if (/no data/i.test(e.message)) input.value = '';
    else alert('Could not read the clipboard: ' + e.message);
  }
}

In an AI-built app the Capacitor-shaped wrapper turns an empty clipboard into '':

import { Clipboard } from '@/lib/appmintNative';

await Clipboard.write({ string: 'ABC-123' });
const { value } = await Clipboard.read();   // '' when the clipboard is empty

Notes: write needs one of string, url or image (a data URL), else it rejects "No data provided". Android 12+ shows its own "pasted from…" notice on read - that is the OS, not the app. The wrapper's write sends only string || url.

Capacitor.Device Capacitor#

Capacitor.Plugins.Device

Example

The official Capacitor Device plugin, built into the app: phone model, OS version, battery, a stable ID and the language.

Returns: Promises. getInfo(){ model, platform, operatingSystem, osVersion, androidSDKVersion, manufacturer, isVirtual, webViewVersion, … }; getId(){ identifier }; getBatteryInfo(){ batteryLevel (0–1), isCharging }; getLanguageCode(){ value }. Needs: nothing to switch on. window.Capacitor exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A Website mode app loads a remote site, and that site never gets window.Capacitor - feature-detect.

Show the phone and battery.

async function showDevice() {
  var cap = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Device;
  if (!cap) { document.getElementById('out').textContent = navigator.userAgent; return; }
  try {
    var info = await cap.getInfo();
    var battery = await cap.getBatteryInfo();
    document.getElementById('out').textContent =
      info.manufacturer + ' ' + info.model + ' - ' + info.operatingSystem + ' ' + info.osVersion +
      ' - battery ' + Math.round(battery.batteryLevel * 100) + '%' + (battery.isCharging ? ' (charging)' : '');
  } catch (err) {
    document.getElementById('out').textContent = 'Could not read device info';
  }
}

A stable ID and the language.

async function helloServer() {
  var cap = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Device;
  if (!cap) return;
  var id = (await cap.getId()).identifier;
  var lang = (await cap.getLanguageCode()).value;     // e.g. "en"
  await fetch('https://api.example.com/hello?lang=' + encodeURIComponent(lang), {
    method: 'POST', body: JSON.stringify({ device: id })
  });
}

In an AI-built app use the wrapper, which also works on the web:

import { Device } from '@/lib/appmintNative';
const info = await Device.getInfo();

Notes: For the Android build ID, security patch, screen, memory and network, the shell's WebToApk.getDeviceInfo() gives more detail. Plugin ids are not hardware ids; they can change after a factory reset.

Capacitor.Dialog Capacitor#

Capacitor.Plugins.Dialog — alert, confirm, prompt

Example

Shows a real Android system dialog: a message, a yes/no question, or a one-line text question (@capacitor/dialog 8.0.1).

Returns: alert({ title?, message, buttonTitle? }) → Promise void. confirm({ title?, message, okButtonTitle?, cancelButtonTitle? }){ value: boolean }. prompt({ title?, message, okButtonTitle?, cancelButtonTitle?, inputPlaceholder?, inputText? }){ value: string, cancelled: boolean }. Needs: nothing to switch on - every app hosts the plugin. window.Capacitor exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A Website mode app loads a remote site, and that site never gets window.Capacitor - feature-detect.

var Dialog = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Dialog;

async function deleteNote(id) {
  if (!Dialog) return;
  var r = await Dialog.confirm({
    title: 'Delete note?',
    message: 'This cannot be undone.',
    okButtonTitle: 'Delete',
    cancelButtonTitle: 'Keep'
  });
  if (r.value) removeNote(id);
}

Alert and prompt:

async function renameFlow() {
  if (!Dialog) return;
  await Dialog.alert({ title: 'Saved', message: 'Your changes are stored.', buttonTitle: 'OK' });

  var p = await Dialog.prompt({ title: 'Rename', message: 'New name', inputText: 'Groceries', inputPlaceholder: 'List name' });
  if (!p.cancelled && p.value.trim()) renameList(p.value.trim());
}

In an AI-built app the wrapper never rejects: dismissal is { value: false } / { cancelled: true }, and on the web it uses the browser's own dialogs:

import { Dialog } from '@/lib/appmintNative';

const { value } = await Dialog.confirm({ title: 'Delete note?', message: 'This cannot be undone.' });
if (value) removeNote(id);

Notes: message is required - without it the plugin rejects "Please provide a message for the dialog". Dialogs are modal; use them only for decisions that must block.

Capacitor.Filesystem Capacitor#

Capacitor.Plugins.Filesystem

Example

The stock Capacitor Filesystem plugin: read and write files in the app's OWN storage (not a folder the user picked - for that use WebToApkFS).

Returns: every method returns a Promise and rejects on failure (for example reading a file that does not exist). readFile{ data }, writeFile{ uri }, readdir{ files: [{ name, type, size, mtime, uri }] }, mkdir / deleteFile → nothing. Needs: nothing to switch on; 'DATA' and 'CACHE' need no permission.

A page has no Directory / Encoding imports, so pass the plain strings 'DATA' (private app files, kept until uninstall) or 'CACHE' (the system may clear it), and 'utf8' for text:

async function saveAndReadBack() {
  var Fs = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Filesystem;
  if (!Fs) { console.log('Filesystem plugin not available (not inside the app)'); return; }

  try {
    await Fs.mkdir({ path: 'notes', directory: 'DATA', recursive: true });
    await Fs.writeFile({ path: 'notes/today.txt', data: 'Hello', directory: 'DATA', encoding: 'utf8', recursive: true });

    var r = await Fs.readFile({ path: 'notes/today.txt', directory: 'DATA', encoding: 'utf8' });
    console.log('text:', r.data);

    var dir = await Fs.readdir({ path: 'notes', directory: 'DATA' });
    dir.files.forEach(function (f) { console.log(f.type, f.name, f.size); });   // type: 'file' | 'directory'

    await Fs.deleteFile({ path: 'notes/today.txt', directory: 'DATA' });
  } catch (e) {
    console.log('File error:', e.message);   // e.g. the file does not exist
  }
}

Leave out encoding to write or read binary data as base64:

var Fs = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Filesystem;
var base64Data = btoa('raw bytes here');   // any base64 string, e.g. from a canvas or FileReader
if (Fs) {
  Fs.writeFile({ path: 'blobs/data.bin', data: base64Data, directory: 'CACHE', recursive: true })
    .then(function (w) { console.log('written to', w.uri); })
    .catch(function (e) { console.log('write failed:', e.message); });
}

In AI-built apps, the @/lib/appmintNative wrapper that uses this plugin is fileTransfer (it stages downloads and uploads in 'CACHE'):

import { fileTransfer } from '@/lib/appmintNative';

const r = await fileTransfer.download('https://example.com/report.pdf', 'report.pdf', {
  onProgress: (p) => console.log(Math.round(p.fraction * 100) + '%'),
});
if (r.path) console.log('saved in the app at', r.path);   // on the web the browser saves it instead

Notes: window.Capacitor exists only inside the app - always feature-detect. Files in 'DATA' are private to the app and deleted on uninstall; to put files where the user can see them, use WebToApkFS (a folder the user grants) or a download.

Capacitor.FileTransfer Capacitor#

Capacitor.Plugins.FileTransfer

Example

The official Capacitor File Transfer plugin: downloads a URL to a file on the phone, or uploads a file from the phone, natively, with progress events.

Returns: downloadFile({url, path, headers?, progress?})Promise<{path}>; uploadFile({url, path, fileKey?, mimeType?, method?, headers?, progress?})Promise<{bytesSent, responseCode, response, headers}>. Progress arrives as 'progress' listener events {type, url, bytes, contentLength, lengthComputable}. Failures reject with {message, code}. Needs: nothing to switch on. window.Capacitor exists only in pages the app itself serves - always feature-detect.

Download into the app's cache with progress:

async function download(url, name, onPct) {
  const P = window.Capacitor && window.Capacitor.Plugins;
  if (!P || !P.FileTransfer || !P.Filesystem) return null;          // not in the app
  const target = await P.Filesystem.getUri({ path: name, directory: 'CACHE' });
  const listener = await P.FileTransfer.addListener('progress', function (p) {
    if (p.url === url && p.lengthComputable) onPct(Math.round(p.bytes / p.contentLength * 100));
  });
  try {
    const r = await P.FileTransfer.downloadFile({ url: url, path: target.uri, progress: true });
    return r.path;
  } catch (e) {
    alert('Download failed: ' + e.message);
    return null;
  } finally {
    listener.remove();
  }
}

Upload a file that is already on the phone:

const P = window.Capacitor && window.Capacitor.Plugins;
if (P && P.FileTransfer) {
  P.FileTransfer.uploadFile({
    url: 'https://example.com/api/upload', path: localFileUri,
    fileKey: 'file', mimeType: 'image/jpeg', headers: { Authorization: 'Bearer ' + token }
  }).then(function (r) { console.log('server said', r.responseCode, r.response); })
    .catch(function (e) { alert('Upload failed: ' + e.message); });
}

Notes: The path must be a file path or file:// URI, not a Blob - write a Blob to the cache with Filesystem.writeFile first. On Android 10 and older, a path in public storage asks for storage permission. In AI-built apps prefer fileTransfer from @/lib/appmintNative, which does all of this and also works on the web.

Capacitor.FileViewer Capacitor#

Capacitor.Plugins.FileViewer

Example

The official Capacitor File Viewer plugin: opens a document or media file in the phone's viewer app, from a web address, a local file path, or a file bundled in the app.

Returns: each method returns a Promise<void> that rejects with {message, code} when nothing can open the file. Needs: nothing to switch on. window.Capacitor exists only in pages the app itself serves - always feature-detect.

async function openPdf(url) {
  const FV = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.FileViewer;
  if (!FV) { window.open(url, '_blank'); return; }          // a normal browser: open a tab
  try {
    await FV.openDocumentFromUrl({ url: url });
  } catch (e) {
    alert('No app on this phone can open this file.');
  }
}

A file on the phone, for example one written with Capacitor.Plugins.Filesystem:

async function openSaved(name) {
  const P = window.Capacitor && window.Capacitor.Plugins;
  if (!P || !P.FileViewer || !P.Filesystem) return;
  const f = await P.Filesystem.getUri({ path: name, directory: 'CACHE' });
  try {
    await P.FileViewer.openDocumentFromLocalPath({ path: f.uri.replace(/^file:\/\//, '') });
  } catch (e) {
    alert('Cannot open ' + name + ': ' + e.message);
  }
}

openDocumentFromResources({path}) opens a file packed in the APK's own assets folder. The same three methods also exist as previewMediaContentFromUrl, previewMediaContentFromLocalPath and previewMediaContentFromResources for video and audio.

Notes: In AI-built apps prefer fileViewer.open(target) from @/lib/appmintNative, which picks the right method and also works on the web. A local path is a plain path (no file://).

Capacitor.Geolocation Capacitor#

Capacitor.Plugins.Geolocation

Example

The official Capacitor Geolocation plugin, built into the app: the phone's position once, or continuous updates.

Returns: Promises. getCurrentPosition(opts){ timestamp, coords: { latitude, longitude, accuracy, altitude, speed, heading, … } }; watchPosition(opts, callback) → the watch id (a string); clearWatch({ id }); checkPermissions() / requestPermissions(){ location, coarseLocation } ('granted' | 'denied' | 'prompt' | …). Needs: GPS and/or Location in Step 4 (Access) - for HTML, ZIP and AI-built apps the build also turns them on by itself when your files call getCurrentPosition or watchPosition. window.Capacitor exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A Website mode app loads a remote site, and that site never gets window.Capacitor - feature-detect.

Position once, asking first.

async function whereAmI() {
  var geo = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Geolocation;
  if (!geo) { showMessage('Open the app to use location.'); return; }
  try {
    var perm = await geo.checkPermissions();
    if (perm.location !== 'granted' && perm.coarseLocation !== 'granted') {
      perm = await geo.requestPermissions();
      if (perm.location !== 'granted' && perm.coarseLocation !== 'granted') {
        showMessage('Location is off for this app.');
        return;
      }
    }
    var pos = await geo.getCurrentPosition({ enableHighAccuracy: true, timeout: 15000 });
    showMessage(pos.coords.latitude.toFixed(5) + ', ' + pos.coords.longitude.toFixed(5));
  } catch (err) {
    showMessage('No location: ' + err.message);   // location off in settings, timeout, …
  }
}

Continuous updates. The callback gets (position, error).

var watchId = null;

async function startTracking() {
  var geo = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Geolocation;
  if (!geo) return;
  watchId = await geo.watchPosition({ enableHighAccuracy: true }, function (pos, err) {
    if (err || !pos) return;
    addPointToMap(pos.coords.latitude, pos.coords.longitude);
  });
}

async function stopTracking() {
  var geo = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Geolocation;
  if (geo && watchId) { await geo.clearWatch({ id: watchId }); watchId = null; }
}

In an AI-built app use the wrapper, which also works on the web:

import { Geolocation } from '@/lib/appmintNative';
const pos = await Geolocation.getCurrentPosition({ enableHighAccuracy: true });

Notes: Plain navigator.geolocation works in the app too. If the location switches were off at build time, the permission is refused with no prompt. On Android 12+ the user may allow only approximate location (coarseLocation granted). Location is personal data: declare it in your Play Data safety form.

Capacitor.Haptics Capacitor#

Capacitor.Plugins.Haptics

Example

The official Capacitor Haptics plugin (@capacitor/haptics), built into the app: light/medium/heavy taps, success/warning/error feedback, selection ticks and plain vibration.

Returns: every method returns a Promise that resolves with nothing. Needs: nothing to switch on - every app declares the vibration permission, whether or not Vibrate is ticked in Step 4 (Access). window.Capacitor exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A Website mode app loads a remote site, and that site never gets window.Capacitor - feature-detect.

function haptics() {
  return window.Capacitor && window.Capacitor.Plugins ? window.Capacitor.Plugins.Haptics : null;
}

async function tap(style) {            // 'LIGHT' | 'MEDIUM' | 'HEAVY'
  var h = haptics();
  if (!h) return;
  try { await h.impact({ style: style }); } catch (e) { console.log('haptics failed', e); }
}

async function result(type) {          // 'SUCCESS' | 'WARNING' | 'ERROR'
  var h = haptics();
  if (h) { try { await h.notification({ type: type }); } catch (e) {} }
}

document.getElementById('add').addEventListener('click', function () { tap('LIGHT'); });
document.getElementById('pay').addEventListener('click', function () { result('SUCCESS'); });

Selection ticks for a picker or slider, and a plain vibration:

var h = window.Capacitor && window.Capacitor.Plugins ? window.Capacitor.Plugins.Haptics : null;
var wheel = document.getElementById('wheel');

wheel.addEventListener('pointerdown', function () { if (h) h.selectionStart(); });
wheel.addEventListener('change', function () { if (h) h.selectionChanged(); });
wheel.addEventListener('pointerup', function () { if (h) h.selectionEnd(); });

document.getElementById('buzz').addEventListener('click', function () {
  if (h) h.vibrate({ duration: 300 });   // milliseconds
});

Notes: The strings are upper case ('HEAVY', not 'heavy'). In AI-built apps, import { Haptics } from '@/lib/appmintNative' gives the same names. For custom rhythms with strength per beat, use Capacitor.Plugins.AppwrightHaptics.

Capacitor.InAppReview Capacitor#

Capacitor.Plugins.InAppReview — requestReview()

Example

Asks Google Play to show its own "rate this app" sheet inside the app. Vendored @capacitor-community/in-app-review (template/plugins/in-app-review).

Returns: Promise void - it resolves when Play's flow ends, and Play never says whether the sheet was shown or what the user did. It rejects when Play could not start the flow. Needs: nothing to switch on; the app must be installed from Google Play for the sheet to appear. window.Capacitor exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A Website mode app loads a remote site, and that site never gets window.Capacitor - feature-detect.

var Review = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.InAppReview;

async function afterThirdOrder() {
  if (!Review) return;
  try {
    await Review.requestReview();
  } catch (e) {
    console.log('review flow not available:', e.message);
  }
  // continue the app flow either way — you cannot know if the user rated
}

In an AI-built app:

import { requestReview } from '@/lib/appmintNative';

// after a success moment, not on launch
const asked = await requestReview();   // false where nothing could be asked (web)

Notes: Play limits how often the sheet appears and does not show it for every call - never tie a reward to it and never ask from a "Rate us" button expecting it to open (open the store listing for that). Ask after a success moment, not at startup. A sideloaded or debug install usually shows nothing.

Capacitor.KeepAwake Capacitor#

Capacitor.Plugins.KeepAwake — keepAwake(), allowSleep(), isKeptAwake(), isSupported()

Example

Stops the screen from dimming and locking while something must stay visible - a recipe, a route, a QR code, a timer. Vendored @capacitor-community/keep-awake (template/plugins/keep-awake).

Returns: keepAwake() / allowSleep() → Promise void. isKeptAwake(){ isKeptAwake: boolean }. isSupported(){ isSupported: true }. Needs: nothing - every app hosts the plugin; no permission. window.Capacitor exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A Website mode app loads a remote site, and that site never gets window.Capacitor - feature-detect.

var KA = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.KeepAwake;

async function startCooking() {
  if (KA) await KA.keepAwake();
  showSteps();
}

async function stopCooking() {
  if (KA) await KA.allowSleep();      // always release when the view closes
}

if (KA) KA.isKeptAwake().then(function (r) { console.log('awake?', r.isKeptAwake); });

In an AI-built app (the Screen Wake Lock API on the web):

import { keepAwake } from '@/lib/appmintNative';

useEffect(() => {
  void keepAwake(true);
  return () => { void keepAwake(false); };
}, []);

Notes: The flag belongs to the app window: it only works while the app is in front, and it stays on until allowSleep() - release it on every exit from the screen. keepAwake() returns false when nothing could keep the screen on.

Capacitor.Keyboard Capacitor#

Capacitor.Plugins.Keyboard — show(), hide(), addListener('keyboardDidShow' | ...)

Example

Opens or closes the software keyboard and tells the page when it appears and how tall it is (@capacitor/keyboard 8.0.5).

Returns: show() / hide() → Promise void. addListener(event, fn) → Promise of a handle with remove(); show events pass { keyboardHeight } in CSS pixels. Events: keyboardWillShow, keyboardDidShow, keyboardWillHide, keyboardDidHide. Needs: nothing to switch on - every app hosts the plugin. window.Capacitor exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A Website mode app loads a remote site, and that site never gets window.Capacitor - feature-detect.

var Keyboard = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Keyboard;

if (Keyboard) {
  Keyboard.addListener('keyboardDidShow', function (info) {
    console.log('keyboard height', info.keyboardHeight);
    document.body.classList.add('typing');       // e.g. hide the bottom tab bar
  });
  Keyboard.addListener('keyboardDidHide', function () {
    document.body.classList.remove('typing');
  });
}

function onSend() {
  if (Keyboard) Keyboard.hide().catch(function () {});   // rejects if no field is focused
}

In an AI-built app use onKeyboard() for layout - it reports only the part of the keyboard the resized page does not already clear - and hideKeyboard() to close it:

import { onKeyboard, hideKeyboard, Keyboard } from '@/lib/appmintNative';

const off = onKeyboard((px) => { composer.style.paddingBottom = `${px}px`; });  // call off() on unmount
hideKeyboard();

const h = Keyboard.addListener('keyboardWillShow', ({ keyboardHeight }) => console.log(keyboardHeight));
h.remove();

Notes: The shell already resizes the page for the keyboard, so do NOT add the full keyboardHeight as padding - a bottom bar is already above the keys. setAccessoryBarVisible, setStyle, setResizeMode and setScroll are not implemented on Android - do not call them. hide() rejects "Can't close keyboard, not currently focused" when nothing has focus.

Capacitor.Network Capacitor#

Capacitor.Plugins.Network — getStatus(), addListener('networkStatusChange')

Example

Tells the page whether the phone is online and on which kind of connection, and when that changes (@capacitor/network 8.0.1).

Returns: getStatus() → Promise { connected: boolean, connectionType: 'wifi' | 'cellular' | 'none' | 'unknown' }. The networkStatusChange event passes the same shape. Needs: nothing to switch on - every app hosts the plugin. window.Capacitor exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A Website mode app loads a remote site, and that site never gets window.Capacitor - feature-detect.

var Network = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Network;

function showBanner(s) {
  document.getElementById('offline').hidden = s.connected;
}

if (Network) {
  Network.getStatus().then(showBanner);
  Network.addListener('networkStatusChange', function (s) {
    showBanner(s);
    if (s.connected) retryQueuedWrites();
    if (s.connectionType === 'cellular') console.log('on mobile data');
  });
}

In an AI-built app:

import { Network, networkStatus, onNetworkChange } from '@/lib/appmintNative';

const { connected, connectionType } = await Network.getStatus();
const now = networkStatus();                        // sync: { connected, type }
const off = onNetworkChange((s) => setOnline(s.connected));   // call off() on unmount

Notes: connected means the phone has a network, not that your server answers - still handle failed requests. The wrapper's Network.getStatus() and networkStatus() read navigator.onLine / navigator.connection, not the plugin, so their type can be 'unknown' or a speed class like '4g'; onNetworkChange() does use the plugin's event in the app.

Capacitor.Preferences Capacitor#

Capacitor.Plugins.Preferences — get / set / remove / keys / clear

Example

A small native key-value store (Android SharedPreferences) for strings (@capacitor/preferences 8.0.1).

Returns: set({ key, value }), remove({ key }), clear() → Promise void; get({ key }){ value } (null when the key is missing); keys(){ keys: string[] }. Needs: nothing to switch on - every app hosts the plugin. window.Capacitor exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A Website mode app loads a remote site, and that site never gets window.Capacitor - feature-detect.

var Prefs = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Preferences;

async function saveTheme(name) {
  if (!Prefs) return false;          // not inside the app
  await Prefs.set({ key: 'theme', value: name });
  return true;
}

async function loadTheme() {
  if (!Prefs) return null;
  var r = await Prefs.get({ key: 'theme' });
  return r.value;            // null if never saved
}

Objects must be turned into a string first:

async function saveUser() {
  if (!Prefs) return;
  await Prefs.set({ key: 'user', value: JSON.stringify({ id: 7, name: 'Asha' }) });
  var u = JSON.parse((await Prefs.get({ key: 'user' })).value || 'null');
  var all = (await Prefs.keys()).keys;
  await Prefs.remove({ key: 'user' });
}

Notes: @/lib/appmintNative has NO Preferences wrapper, on purpose: localStorage is already real and persistent in the app, and a second store would split one app's data across two places. AI-built apps should keep using localStorage (or the data runtime). Use Preferences in a hand-written page only if you want all of your settings there, and pick one store per value. configure({ group }) changes the store name (default CapacitorStorage).

Capacitor.PrivacyScreen Capacitor#

Capacitor.Plugins.PrivacyScreen — enable(), disable(), isEnabled()

Example

The official @capacitor/privacy-screen plugin: blocks screenshots and screen recording and hides the app's content in the recent-apps view. Baked so imported Capacitor projects that call it work.

Returns: enable({android}){success}; disable(){success}; isEnabled(){enabled}. Needs: nothing to switch on.

const Privacy = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.PrivacyScreen;

async function openVault() {
  if (Privacy) await Privacy.enable({ android: { privacyModeOnActivityHidden: 'dim' } });
  showVault();
}
async function closeVault() {
  if (Privacy) await Privacy.disable();
}

Notes: privacyModeOnActivityHidden: 'none', 'dim' or 'splash'. Code written for the app itself should use WebToApk.setSecureScreen(true) / privacyScreen.enable() from @/lib/appmintNative, which follows the page (released when the app leaves it); mixing both, the last call wins.

Capacitor.ScreenBrightness Capacitor#

Capacitor.Plugins.ScreenBrightness — getBrightness(), setBrightness({ brightness })

Example

Sets the app window's screen brightness - full brightness for a boarding pass or QR code, then back. Vendored @capacitor-community/screen-brightness (template/plugins/screen-brightness).

Returns: getBrightness() → Promise { brightness } (0-1, or -1 when the app has not overridden it and the screen follows the system). setBrightness({ brightness }) → Promise void. Needs: nothing - every app hosts the plugin; no permission (it changes only this app's window, not the system setting). window.Capacitor exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A Website mode app loads a remote site, and that site never gets window.Capacitor - feature-detect.

var SB = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.ScreenBrightness;

async function showTicket() {
  if (SB) await SB.setBrightness({ brightness: 1 });     // full brightness
  document.getElementById('qr').hidden = false;
}

async function hideTicket() {
  if (SB) await SB.setBrightness({ brightness: -1 });    // -1 = follow the system again
  document.getElementById('qr').hidden = true;
}

In an AI-built app:

import { brightness } from '@/lib/appmintNative';

await brightness.set(1);        // clamped to 0–1; false where not available (web)
// ... on leaving the screen
await brightness.restore();
const level = await brightness.get();   // -1 = following the system / not available

Notes: Always pass a number - setBrightness({}) has no value to apply. Restore with -1 when the view closes; the override also ends when the app closes. A web page cannot drive the backlight, so can('brightness') is 'none' in a browser.

Capacitor.ScreenOrientation Capacitor#

Capacitor.Plugins.ScreenOrientation — orientation(), lock({ orientation }), unlock()

Example

Reads the screen orientation, pins it for one view (a game, a chart, a signature pad) and releases it (@capacitor/screen-orientation 8.0.1).

Returns: orientation() → Promise { type } (e.g. 'portrait-primary'). lock() / unlock() → Promise void. Event screenOrientationChange passes { type }. Needs: nothing to switch on - every app hosts the plugin. window.Capacitor exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A Website mode app loads a remote site, and that site never gets window.Capacitor - feature-detect.

orientation values: 'portrait', 'landscape', 'portrait-primary', 'portrait-secondary', 'landscape-primary', 'landscape-secondary', 'natural', 'any'.

var SO = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.ScreenOrientation;

async function openChart() {
  if (SO) await SO.lock({ orientation: 'landscape' });
  showChart();
}

async function closeChart() {
  if (SO) await SO.unlock();          // always release when leaving the view
  hideChart();
}

if (SO) {
  SO.orientation().then(function (o) { console.log('now', o.type); });
  SO.addListener('screenOrientationChange', function (o) { console.log('rotated to', o.type); });
}

In an AI-built app:

import { lockOrientation, ScreenOrientation } from '@/lib/appmintNative';

const ok = await lockOrientation('landscape');   // false when refused (always on desktop)
await lockOrientation('any');                    // release

await ScreenOrientation.lock({ orientation: 'portrait' });
await ScreenOrientation.unlock();

Notes: The wrappers only know portrait / landscape / any: ScreenOrientation.lock({ orientation: 'landscape-secondary' }) locks to plain landscape. Give the user a way out of a locked view.

Capacitor.ScreenReader Capacitor#

Capacitor.Plugins.ScreenReader — isEnabled(), speak({ value, language }), addListener('stateChange')

Example

Tells the page whether TalkBack is on, and speaks an announcement (@capacitor/screen-reader 8.0.1).

Returns: isEnabled() → Promise { value: boolean }. speak() → Promise void. The stateChange event passes { value: boolean } when TalkBack is switched on or off. Needs: nothing to switch on - every app hosts the plugin. window.Capacitor exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A Website mode app loads a remote site, and that site never gets window.Capacitor - feature-detect.

var SR = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.ScreenReader;

function setCalmMode(on) {
  document.documentElement.classList.toggle('no-motion', on);   // shorten animations
}

if (SR) {
  SR.isEnabled().then(function (r) { setCalmMode(r.value); });
  SR.addListener('stateChange', function (s) { setCalmMode(s.value); });
}

async function announceSaved() {
  if (SR) await SR.speak({ value: 'Order placed', language: 'en' });
}

In an AI-built app (speak uses text-to-speech on the web; isEnabled() is false there):

import { screenReader } from '@/lib/appmintNative';

const on = await screenReader.isEnabled();
const off = screenReader.onChange((enabled) => setCalmMode(enabled));
await screenReader.speak('Order placed', 'en');

Notes: language defaults to 'en'. Prefer normal accessible markup (labels, aria-live regions) for most announcements; use speak for short, important state changes. There is no web API to detect a screen reader, so can('screenReader') is 'none' in a browser.

Capacitor.Share Capacitor#

Capacitor.Plugins.Share — share(options), canShare()

Example

Opens the Android share sheet with a title, text and/or a link (@capacitor/share 8.0.1).

Returns: share() → Promise { activityType } (the chosen app's component name, may be ''); it REJECTS when the user closes the sheet. canShare() → Promise { value: boolean }. Needs: nothing to switch on - every app hosts the plugin. window.Capacitor exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A Website mode app loads a remote site, and that site never gets window.Capacitor - feature-detect.

async function shareLink() {
  var Share = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Share;
  if (!Share) { alert('Sharing is not available here'); return; }
  // Only pass the fields you have. A present but empty `url` is rejected ("Unsupported url").
  var opts = { title: 'My list', text: 'Milk, eggs, bread' };
  var link = 'https://example.com/list/42';
  if (link) opts.url = link;
  try {
    var r = await Share.share(opts);
    console.log('shared to', r.activityType);
  } catch (e) {
    console.log('share cancelled or failed:', e.message);   // "Share canceled" on dismiss
  }
}

In an AI-built app the wrapper has the same shape and also rejects on cancel:

import { Share, can } from '@/lib/appmintNative';

if (can('share') !== 'none') {
  try {
    await Share.share({ title: 'My list', text: 'Milk, eggs, bread', url: 'https://example.com/list/42' });
  } catch {
    // the user closed the sheet — not an error to show
  }
}

Notes: url must be http(s):// or file://. dialogTitle sets the chooser heading. Sharing files needs file:// paths the app owns; from a page, share files with navigator.share({ files }) instead. Only one share can be open at a time ("Can't share while sharing is in progress").

Capacitor.SpeechRecognition Capacitor#

Capacitor.Plugins.SpeechRecognition

Example

Turns the user's speech into text with Android's speech recognizer (the community @capacitor-community/speech-recognition plugin, built into the app). Android WebView has no Web Speech recognition of its own, so this is the way to do dictation.

Returns: Promises. available(){ available }. checkPermissions() / requestPermissions(){ speechRecognition: 'granted' | 'denied' | 'prompt' }. start({ partialResults: false }){ status: 'success', matches: [...] } after the user stops talking; it rejects on errors such as no speech or no match. Needs: tick Mic in Step 4 (Access) when you build (for HTML, ZIP and AI-built apps the build also adds the microphone permission by itself when your files mention SpeechRecognition), and the user must allow the microphone. window.Capacitor exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A Website mode app loads a remote site, and that site never gets window.Capacitor - feature-detect.

One-shot dictation (the same way @/lib/appmintNative speech uses it):

var SR = window.Capacitor && window.Capacitor.Plugins ? window.Capacitor.Plugins.SpeechRecognition : null;

async function dictate() {
  var out = document.getElementById('text');
  if (!SR || !(await SR.available()).available) { out.textContent = 'Voice input is not available on this phone.'; return; }

  var p = (await SR.checkPermissions()).speechRecognition;
  if (p !== 'granted') p = (await SR.requestPermissions()).speechRecognition;
  if (p !== 'granted') { out.textContent = 'Microphone permission was not given.'; return; }

  try {
    var r = await SR.start({ language: 'en-US', maxResults: 3, partialResults: false, popup: false });
    out.textContent = r.matches && r.matches.length ? r.matches[0] : '';
  } catch (e) {
    out.textContent = 'Did not catch that. Try again.';
  }
}

document.getElementById('mic').addEventListener('click', dictate);

Live text while the user speaks (partialResults: true), and the listening state:

// Add the listeners ONCE, when the page starts.
if (SR) {
  SR.addListener('partialResults', function (d) {
    // Words as they come, and then the final text, all arrive here.
    if (d.matches && d.matches.length) document.getElementById('text').textContent = d.matches[0];
  });
  SR.addListener('listeningState', function (d) {
    // 'started' = the user began speaking, 'stopped' = the user stopped speaking
    document.body.classList.toggle('listening', d.status === 'started');
  });
}

document.getElementById('mic-live').addEventListener('click', async function () {
  if (!SR) return;
  try {
    await SR.start({ language: 'en-US', partialResults: true, popup: false });   // resolves at once, no matches
  } catch (e) {
    document.getElementById('text').textContent = 'Could not start: ' + e.message;
  }
});

document.getElementById('stop').addEventListener('click', function () { if (SR) SR.stop(); });

Notes:

  • In live mode (partialResults: true), the final text arrives after the 'stopped' state, so do not remove the partialResults listener on 'stopped'. An error after the start (no speech, no match) is not reported in this mode.
  • language is a tag like 'en-US' or 'ta-IN' (default: the phone's language). popup: true shows Google's own listening dialog instead. getSupportedLanguages(){ languages } and isListening(){ listening } also exist.
  • start() rejects with 'Missing permission' if you did not ask first. Listening stops by itself after a pause in speech.

Capacitor.SplashScreen Capacitor#

Capacitor.Plugins.SplashScreen — show(), hide()

Example

The @capacitor/splash-screen API over the app's own branded launch splash, so imported Capacitor / AI Studio projects that call SplashScreen.hide() once their first screen is ready work in the Android app.

Returns: hide() → Promise (the splash goes once the creator's minimum splash time has passed); show({showDuration, autoHide}) → Promise (the splash comes back over the page and, with autoHide - the default - goes after showDuration ms, default 3000). Needs: nothing to switch on.

const Splash = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.SplashScreen;

async function boot() {
  await loadFirstScreen();
  if (Splash) await Splash.hide();     // otherwise the shell hides it at first paint
}
boot();

Notes: without a hide() call the splash still goes at the first paint, like every app. AI-built apps use splash.hold() / splash.ready() from @/lib/appmintNative.

Capacitor.StatusBar Capacitor#

Capacitor.Plugins.StatusBar — setStyle, setBackgroundColor, show, hide, getInfo

Example

Changes the status bar's icon style, colour and visibility (@capacitor/status-bar 8.0.2).

Returns: every method → Promise void, except getInfo(){ visible, style, color, overlays, height }. Needs: nothing to switch on - every app hosts the plugin. window.Capacitor exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A Website mode app loads a remote site, and that site never gets window.Capacitor - feature-detect.

style names the BACKGROUND it suits: 'DARK' = light icons (for a dark bar), 'LIGHT' = dark icons, 'DEFAULT' = follow the system.

var StatusBar = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.StatusBar;
if (StatusBar) {
  StatusBar.setStyle({ style: 'DARK' });          // light icons
  StatusBar.hide();                               // e.g. while a video plays
  // later
  StatusBar.show();
}

Colour the bar with the shell bridge, not the plugin. On Android 15+ the plugin's setBackgroundColor does nothing (edge-to-edge ignores it). The shell paints the strip itself and takes the icon style in the same call:

if (window.WebToApk && window.WebToApk.setStatusBarColor) {
  window.WebToApk.setStatusBarColor('#0B0F1A', 'light');   // dark bar, light icons
} else if (StatusBar) {
  StatusBar.setBackgroundColor({ color: '#0B0F1A' });      // hex only, e.g. #RRGGBB
}

In an AI-built app use setStatusBar(color, contentStyle); it prefers the shell bridge and sets colour and icons in one call:

import { setStatusBar, StatusBar } from '@/lib/appmintNative';

setStatusBar('#0B0F1A', 'light');                       // dark bar, light icons
await StatusBar.setBackgroundColor({ color: '#FFFFFF' }); // colour only; icons picked from luminance

Notes: setBackgroundColor rejects a colour that is not a hex string. In fullscreen / kiosk builds the bars are hidden by the shell. In the Android app the wrapper's StatusBar.setStyle() alone changes nothing (the bridge needs a colour to paint) - pass the style to setStatusBar() together with a colour.

Capacitor.TextToSpeech Capacitor#

Capacitor.Plugins.TextToSpeech — speak(), stop(), getSupportedLanguages(), getSupportedVoices()

Example

The community @capacitor-community/text-to-speech plugin over the phone's text-to-speech engine. Baked so imported Capacitor projects that call it work.

Returns: speak({text, lang, rate, pitch, volume, voice}) → Promise that resolves when the sentence has been spoken; stop(); getSupportedLanguages(){languages}; getSupportedVoices(){voices}; isLanguageSupported({lang}){supported}. Needs: nothing to switch on.

const Tts = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.TextToSpeech;

async function readAloud(text) {
  if (!Tts) { speechSynthesis.speak(new SpeechSynthesisUtterance(text)); return; }
  await Tts.speak({ text: text, lang: 'en-US', rate: 1.0, pitch: 1.0, volume: 1.0 });
}
document.getElementById('stop').onclick = () => Tts && Tts.stop();

Notes: window.speechSynthesis works in the app too (the shell's own engine) and is what AI-built apps use through speak(); this plugin is the Capacitor-shaped path.

Capacitor.TextZoom Capacitor#

Capacitor.Plugins.TextZoom — get(), getPreferred(), set({ value })

Example

Reads and changes the text size of the app's page, for an in-app "text size" control (@capacitor/text-zoom 8.0.1).

Returns: get() → Promise { value } (current zoom, 1 = 100%). getPreferred(){ value } (the size the user picked in Android settings). set({ value }) → Promise void. Needs: nothing to switch on - every app hosts the plugin. window.Capacitor exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A Website mode app loads a remote site, and that site never gets window.Capacitor - feature-detect.

var TZ = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.TextZoom;

async function biggerText() {
  if (!TZ) return;
  var now = (await TZ.get()).value;
  await TZ.set({ value: Math.min(2, now + 0.1) });
}

async function resetText() {
  if (!TZ) return;
  var pref = (await TZ.getPreferred()).value;   // e.g. 1.15 when the phone uses large text
  await TZ.set({ value: pref });
}

In an AI-built app (on the web it scales the root font size instead):

import { textZoom } from '@/lib/appmintNative';

const size = await textZoom.get();
await textZoom.set(size + 0.1);                     // clamped to 0.5–3
await textZoom.set(await textZoom.getPreferred());  // back to the system size

Notes: The app already follows the system text size; this is for an extra in-app control. Test your layout at large sizes - fixed heights on text controls will clip.

Capacitor.Toast Capacitor#

Capacitor.Plugins.Toast — show({ text, duration, position })

Example

Shows a short Android system toast such as "Saved" or "Copied" (@capacitor/toast 8.0.1).

Returns: Promise void. Needs: nothing to switch on - every app hosts the plugin. window.Capacitor exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A Website mode app loads a remote site, and that site never gets window.Capacitor - feature-detect.

var Toast = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Toast;

function saved() {
  if (Toast) Toast.show({ text: 'Saved', duration: 'short', position: 'bottom' });
}

duration is 'short' (about 2 s) or 'long' (about 3.5 s). position is 'top', 'center' or 'bottom'.

In an AI-built app use toast() or the Capacitor-shaped Toast; on the web they draw a snackbar in the app's colours:

import { Toast, toast } from '@/lib/appmintNative';

await Toast.show({ text: 'Added to list', duration: 'long' });
toast('Copied');   // short form, same result

Notes: text is required ("Must provide text"). The system draws the toast in the phone maker's style, and newer Android versions ignore position for text toasts. A toast is easy to miss - never use it for an error the user must act on; use Dialog.alert for that.

Capacitor.Torch Capacitor#

Capacitor.Plugins.Torch — enable(), disable(), toggle(), isEnabled(), isAvailable()

Example

The phone's flashlight, with the @capawesome/capacitor-torch API. The app shell's own plugin (TorchPlugin.kt).

Returns: enable() / disable() / toggle() → Promise (rejects CAMERA_IN_USE while the app's own camera stream is open, UNAVAILABLE without a flash); isEnabled(){enabled}; isAvailable(){available}. Needs: nothing to switch on and no permission.

const Torch = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Torch;

async function setupLightButton() {
  if (!Torch || !(await Torch.isAvailable()).available) { lightBtn.hidden = true; return; }
  lightBtn.onclick = async () => {
    try {
      await Torch.toggle();
      lightBtn.classList.toggle('on', (await Torch.isEnabled()).enabled);
    } catch (e) {
      alert(e.message);
    }
  };
}

Notes: the light follows the system state (turning it off from the quick-settings tile reads back as off) and is switched off when the app closes. While a getUserMedia stream is open, light it through the track: track.applyConstraints({ advanced: [{ torch: true }] }). AI-built apps use torch from @/lib/appmintNative.

Generated from the app runtime and its example files on every docs build. Read it as Markdown · All families.

Checked against the shipped bridge on 2026-09-23.