# Fliplet Developers documentation URL: https://developers.fliplet.com/ # Fliplet Developers documentation Reference and guides for extending Fliplet apps with the JS API, REST API, and custom components, themes, and menus.

Customize your apps with our low code technologies

Welcome to the Fliplet developers documentation website. Here you can learn how to extend your Fliplet Apps, create new components, themes and menus.

Start by having a look at our JS API documentation to learn how to interact with our core framework and components to extend your apps.

Write code that works on any device and platform

Fliplet allows you to code once and have your app fully function on all the platforms we support, including iOS, Android and Web.

Stop worrying about compatibility and let us do the heavy lifting for you. Just focus on coding!

Integrate with on-prem data

Use our open-source Data Integration Service software to fetch data from your on-prem infrastructure and integrate a secure login system to your apps using Single Sign-On with SAML2 or other identity providers. The choice is yours!

Create complex workflows

Start with a simple proof-of-concept, then start leveraging our technology to virtually built anything else!

We have a JS API for any occasion, give it a read and find out how easy is to get started.

--- {% raw %}

JavaScript API Documentation

Learn how to use our SDK and enrich apps within seconds.

REST API Documentation

Integrate your backend with our easy-to-use RESTful APIs.

{% endraw %} To get a brief introduction to the technologies we use and the stack of the platform, we recommend having a quick read at the **[Introduction to the platform](Introduction)** page of the documentation. --- ### Upcoming and newly released features Here's a short list of requested features that are coming up on our platform in the near future or have just been released.
Available to all customers

App Actions

Learn how App Actions allow you to define scheduled automations for your app screens.

Open beta

AI

Learn how to use and integrate AI (Artificial Intellicence) capabilities to your apps.

``` ## Session Check Check whether the user is logged in. Use this in your App shell component or route guards to protect screens. ```js async function isLoggedIn() { try { var session = await Fliplet.User.getCachedSession(); // getCachedSession returns the locally cached session (works offline). // session.entries.dataSource exists if the user logged in via email/password. return !!(session && session.entries && session.entries.dataSource); } catch (error) { return false; } } ``` `Fliplet.User.getCachedSession()` is fast and works offline because it reads from local storage. Use it for UI decisions (show/hide content). For server-verified session checks, use `Fliplet.Session.get()` instead (requires network). ### Get the Logged-In User's Data ```js async function getCurrentUser() { var session = await Fliplet.User.getCachedSession(); if (!session || !session.entries || !session.entries.dataSource) { return null; // Not logged in } // Returns all columns from the user's Data Source record // e.g., { Email: 'jane@company.com', Name: 'Jane Smith', Role: 'Editor' } return session.entries.dataSource.data; } ``` ## Logout Clear the session and redirect to the login screen. V3 uses History API routing on every platform, so redirect via your router (or `history.pushState` + `Fliplet.Router.getBasePath()`) — never `window.location.hash`. See [V3 Routing](routing) for the full contract. ```js // Called from a component that has a router instance in scope. async function logout(router) { await Fliplet.Session.logout('dataSource'); router.push('/login'); } ``` If you don't have a router handy (e.g., a framework-agnostic helper), use the History API directly: ```js async function logout() { await Fliplet.Session.logout('dataSource'); history.pushState({}, '', Fliplet.Router.getBasePath() + '/login'); // Trigger your router's re-render. In vanilla setups, dispatch a popstate. window.dispatchEvent(new PopStateEvent('popstate')); } ``` `Fliplet.Session.logout('dataSource')` clears the dataSource passport. To log out from all passport types (dataSource, saml2, flipletLogin), call `Fliplet.Session.logout()` with no arguments. ## Protected Routes with Vue Router Guards In V3 apps using Vue Router, protect routes by checking the session before navigation. This section assumes the router was built per the V3 routing contract (History API, base path from `Fliplet.Router.getBasePath()`, routes from `Fliplet.Router.getRouteManifest()`). See the [Vue Router 4 example](routing#vue-router-4-vue-3) in the V3 Routing doc — or the sibling examples for other frameworks — before wiring the guard below. ### In the App Shell (App.vue) Add a `beforeEach` guard to the router in your boot template's `initVueApp()` function. This does NOT go inside an SFC file. It goes in the boot HTML: ```js // Inside initVueApp(), after creating the router: router.beforeEach(function(to, from, next) { // Define which routes are public (accessible without login) var publicRoutes = ['/login', '/forgot-password', '/reset-password']; if (publicRoutes.indexOf(to.path) !== -1) { return next(); // Public route — allow } // Check session for protected routes Fliplet.User.getCachedSession().then(function(session) { if (session && session.entries && session.entries.dataSource) { next(); // Logged in — allow } else { next('/login'); // Not logged in — redirect to login } }).catch(function() { next('/login'); // Error checking session — redirect to login }); }); ``` ### Route Configuration Define your routes so the login screen is accessible without authentication: ```js routes: [ { path: '/', redirect: '/home' }, { path: '/login', name: 'Login', component: function() { return loadComponent(COMPONENT_FILES.Login); } }, { path: '/forgot-password', name: 'ForgotPassword', component: function() { return loadComponent(COMPONENT_FILES.ForgotPassword); } }, { path: '/home', name: 'Home', component: function() { return loadComponent(COMPONENT_FILES.Home); } }, { path: '/settings', name: 'Settings', component: function() { return loadComponent(COMPONENT_FILES.Settings); } } ] ``` ## Forgot Password Password reset uses the existing Data Source validation APIs. Code generation, storage, email delivery, and verification all happen server-side. The client orchestrates the UI steps. The flow uses three existing APIs: 1. `dataSource.sendValidation()` — server generates a code, stores it, sends it via email 2. `dataSource.validate()` — server verifies the code, marks the user as needing a password reset 3. `Fliplet.Session.updateUserPassword()` — server updates the password **Do NOT build custom password reset logic.** These APIs handle code generation, expiry, and verification server-side. The client never sees the reset code. ### Forgot Password Screen (Vue SFC) ```html ``` **API Reference for the forgot-password flow:** | Step | Client calls | Server does | |---|---|---| | Send code | `dataSource.sendValidation({ type: 'email', where: { Email: '...' } })` | Generates code, stores on entry, sends email. Returns 204 (no info leak). | | Verify code | `dataSource.validate({ type: 'email', where: { code: '...' }, requiresPasswordReset: true })` | Validates code, checks expiry (24h default), creates temporary session. | | Reset password | `Fliplet.Session.updateUserPassword({ newPassword, passwordColumn })` | Updates password column on data source entry, logs out user. | This is the same flow the V2 login widget uses. All security-critical operations (code generation, validation, password storage) happen server-side. ## Patterns — DO and DON'T ```js // DO: Use Fliplet.Session.authorize() for email/password login await Fliplet.Session.authorize({ passport: 'dataSource', dataSourceId: id, where: { Email, Password } }); // DON'T: Store session tokens in localStorage // localStorage is partitioned in cross-origin iframes (Studio preview). // Fliplet.Session handles storage internally. // DO: Use Fliplet.User.getCachedSession() for session checks (fast, works offline) var session = await Fliplet.User.getCachedSession(); // DON'T: Use Fliplet.Session.get() for every session check // Session.get() makes a network request. Use getCachedSession() for UI decisions. // DO: Use Vue Router guards for protected routes (see above) // DON'T: Check the session inside every component's mounted() hook // DO: Use Fliplet.Session.logout('dataSource') for dataSource logouts // DON'T: Clear cookies or localStorage manually — the SDK handles cleanup // DO: Show generic "Invalid email or password" errors // DON'T: Reveal whether the email or password was wrong separately ``` ## Related - [V3 Routing](routing) — base path, route manifest, `resolveRoute`, and post-login redirect pattern. - [V3 App Bootstrap](app-bootstrap) — the three boot-HTML constraints every V3 app must satisfy. - [Session JS APIs](../fliplet-session) — full session API reference - [Login Component](../components/login) — V2 login component hooks - [Email Verification](../components/email-verification) — passwordless login flow - [Data Source Security](../../Data-source-security) — security rules for user data sources - [App Security](../../App-security) — app-level access control rules --- # Fliplet Router JS API URL: https://developers.fliplet.com/API/v3/fliplet-router.html # Fliplet Router JS API `Fliplet.Router` is the client-side routing helper for V3 SPA apps. It reads the route manifest from `app.settings.v3`, normalizes the base path for the current hosting context (slug-hosted web, Studio preview iframe, native shell), and performs server-ACL-backed access checks for each route.

Fliplet.Router is available on V3 apps only and is auto-loaded at boot. It depends on fliplet-core (Fliplet.Env, Fliplet.User) and fliplet-media (Fliplet.Media.getContents).

For the full routing contract, per-framework integration examples, and forbidden patterns, see [V3 routing](routing). This page is the API reference only. ## Contents - [Methods](#methods) - [getBasePath()](#flipletroutergetbasepath) - [getRouteManifest()](#flipletroutergetroutemanifest) - [getRouteConfig(path)](#flipletroutergetrouteconfigpath) - [resolveRoute(pathOrRoute)](#flipletrouterresolveroutepathorroute) - [Reason codes](#reason-codes) - [Manifest shape](#manifest-shape) - [Related](#related) ## Methods ### `Fliplet.Router.getBasePath()` Returns the base path used by the router's history mode, normalized with a trailing slash so string concatenation doesn't produce `//route`. **Returns:** `String` The value depends on the hosting context: | Context | Example value | |---|---| | Root-hosted web app | `/` | | Slug-hosted web app | `/my-slug/` | | Studio preview iframe | `/v1/apps/42/pages/99/preview/` | | Native shell | Computed by the shell at runtime | ```js var base = Fliplet.Router.getBasePath(); // Pass to Vue Router 4: var history = VueRouter.createWebHistory(base); // Pass to React Router 6: var router = ReactRouterDOM.createBrowserRouter(routes, { basename: base }); ```

Never hardcode '/'. Slug-hosted apps, preview iframes, and native shells all mount the SPA at a different base.

### `Fliplet.Router.getRouteManifest()` Returns the route manifest stored in `app.settings.v3`. Defaults are provided for every field so callers can use the return value without null checks. **Returns:** `Object` with the following properties: | Property | Type | Default | Description | |---|---|---|---| | `routes` | `Array` | `[]` | Route entries. See [Manifest shape](#manifest-shape). | | `defaultRoute` | `String` | `'/'` | Path to redirect to from `/`. | | `authRedirect` | `String` | `'/login'` | Path to redirect to when a route is denied. | ```js var manifest = Fliplet.Router.getRouteManifest(); manifest.routes.forEach(function(r) { console.log(r.name, r.path, r.fileId, r.public); }); console.log(manifest.defaultRoute); // '/home' console.log(manifest.authRedirect); // '/login' ``` ### `Fliplet.Router.getRouteConfig(path)` Looks up a single route entry from the manifest by path. The lookup is path-based and normalizes leading slashes (`home` and `/home` match the same route). **Parameters:** | Name | Type | Description | |---|---|---| | `path` | `String` | Route path, with or without a leading slash. | **Returns:** `Object | undefined`. The matching route entry from `manifest.routes`, or `undefined` if no route matches. ```js var route = Fliplet.Router.getRouteConfig('/home'); if (route) { console.log(route.fileId); // 222 console.log(route.public); // true } else { // Path isn't in the manifest } ``` ### `Fliplet.Router.resolveRoute(pathOrRoute)` Resolves a route to its access decision **and** its screen source. On success, `result.content` is the screen's source already loaded via `Fliplet.Media.getContents` — render it directly rather than re-fetching. The server's media ACL is the source of truth; this method either fast-fails when the outcome is known (no session, unknown route) or derives the decision from the server's 401/403 response. **Parameters:** | Name | Type | Description | |---|---|---| | `pathOrRoute` | `String \| Object` | A route path (string) or a route entry object from the manifest. | **Returns:** `Promise` that: - **Resolves** with an access decision (see shapes below). - **Rejects** with the underlying error on transient or infra failures (network errors, 5xx responses) so callers can show an error UI instead of redirecting silently. **Resolution shape on success:** ```js { allowed: true, content: '', route: { name: 'Home', path: '/home', fileId: 222, public: true } } ``` **Resolution shape on denial:** ```js { allowed: false, redirectTo: '/login', // manifest.authRedirect reason: 'no-session', // see Reason codes below status: 401 // only present when reason is 'media-denied' } ```

Don't call Fliplet.Media.getContents(fileId) yourself. resolveRoute already does this internally and returns the content in result.content. A second fetch duplicates the round trip, can race with the access check, and fails outright on private media where auth headers aren't applied.

**Example:** ```js Fliplet.Router.resolveRoute('/my-account').then(function(result) { if (!result.allowed) { // Redirect to the auth screen (or wherever the server says) navigate(result.redirectTo); return; } // Mount the screen using result.content mountScreen(result.content, result.route); }, function(err) { // Transient failure (network, 5xx). Show an error UI rather than redirecting. showErrorScreen(err); }); ``` **Passing a route object instead of a path:** If you already have a route entry from the manifest, you can pass it directly to skip the path lookup: ```js var manifest = Fliplet.Router.getRouteManifest(); var route = manifest.routes[0]; Fliplet.Router.resolveRoute(route).then(function(result) { // ... }); ``` ## Reason codes When `resolveRoute` resolves with `allowed: false`, the `reason` property indicates why. Handle each case explicitly. | `reason` | Triggered when | `redirectTo` | `status` | |---|---|---|---| | `unknown-route` | The path doesn't match any manifest entry, or the matched entry has no `fileId`. | `manifest.authRedirect` | Not set | | `no-session` | The route is non-public and `Fliplet.User.getCachedSession()` returned no session. | `manifest.authRedirect` | Not set | | `media-denied` | The server returned `401` or `403` when fetching the screen source. | `manifest.authRedirect` | `401` or `403` |

The manifest's public: true flag is advisory. It just lets the client skip a known-401 round trip. Flipping public: true in the manifest without updating the media file's access rule grants no access; the server still returns 401 and resolveRoute resolves with reason: 'media-denied'.

Transient errors (network failures, 5xx responses) do not resolve with a reason. They **reject** the Promise with the underlying error so you can distinguish "user can't access this" from "infrastructure is broken". ## Manifest shape The manifest is stored at `app.settings.v3` and emitted to the runtime via `window.ENV.appSettings`. See [V3 app settings convention](app-settings) for how settings are stored and filtered. ```json { "routes": [ { "name": "Home", "path": "/home", "fileId": 222, "public": true }, { "name": "Login", "path": "/login", "fileId": 444, "public": true }, { "name": "MyAccount", "path": "/my-account", "fileId": 333 } ], "defaultRoute": "/home", "authRedirect": "/login" } ``` Each route entry has the following properties: | Property | Type | Required | Description | |---|---|---|---| | `name` | `String` | Yes | Display name for the route. Used in Studio UI and as a named route in framework routers. | | `path` | `String` | Yes | URL path the route is mounted at. Must begin with `/`. | | `fileId` | `Number` | Yes | Numeric `id` of the media file holding the screen source. `resolveRoute` fetches this via `Fliplet.Media.getContents`. | | `public` | `Boolean` | No | If `true`, the client skips the session check before fetching media. Defaults to `false`. Advisory only; the server media ACL is still enforced. | Update the manifest via the App Settings API (`PUT /v1/apps/:id` with `settings.v3`) or the Studio routing UI whenever you add or remove a user-visible route. ## Related - [V3 routing](routing). Full routing contract, per-framework examples, forbidden patterns, and post-login redirect. - [V3 app bootstrap constraints](app-bootstrap). Three constraints every V3 boot HTML must satisfy. - [V3 app settings convention](app-settings). Where the route manifest is stored (`app.settings.v3`). - [Media JS APIs](../fliplet-media). `Fliplet.Media.getContents` details. --- # V3 Alpine.js apps URL: https://developers.fliplet.com/API/v3/frameworks/alpine.html # V3 Alpine.js apps Alpine fits V3 well because it's attribute-driven HTML with no compile step. Components are just `
` — the kind of markup V3 already evaluates natively. Best fit: form-heavy UIs, settings panels, and single-page CRUD where you'd otherwise reach for jQuery. ## Loading the framework Add `alpinejs` via `add_dependencies`, then: ```js const Alpine = await Fliplet.require.lazy('alpinejs'); Alpine.start(); ``` **Call `Alpine.start()` inside your `Fliplet().then(...)` callback**, not at module load. Alpine scans the DOM and initializes `x-data` components when it starts — if it starts before the Fliplet runtime resolves, any `x-init` or `x-data` expression that calls `Fliplet.*` will error. ## Features that need a build step None that Alpine itself requires. The generic V3 constraints still apply: - No bare ESM `import` across uploaded source files — use `Fliplet.require.lazy` for external deps. - No TypeScript. - No CSS preprocessing. ## Wiring to Fliplet.Router Alpine has no router. Pair it with History API routing the same way as vanilla JS: ```js function goTo(path) { history.pushState({}, '', Fliplet.Router.getBasePath() + path); window.dispatchEvent(new PopStateEvent('popstate')); } ``` Use `Fliplet.Router.resolveRoute(path)` in your `popstate` handler. See [V3 routing](../routing). ## Binding Fliplet.Media.authenticate Compute the authenticated URL inside `x-init` or a method, then set a data property: ```html ``` The `:src="src"` binding updates once the promise resolves. Do not try `:src="Fliplet.Media.authenticate(rawUrl)"` — that binds the Promise, not the URL. ## Common errors | Symptom in `get_preview_logs('errors')` | Cause | Fix | |---|---|---| | `Alpine Expression Error: Fliplet is not defined` | `Alpine.start()` called before `Fliplet()` resolved | Move `Alpine.start()` inside `Fliplet().then(...)` | | `` renders with a Promise in `src` | Using `Fliplet.Media.authenticate(url)` directly in `:src` | Resolve into a state variable first (see above) | | Components don't react | `Alpine.start()` called before the target markup was in the DOM | Ensure the screen source is inserted before Alpine starts, or use `Alpine.initTree(el)` on newly inserted markup | ## DO / DON'T - DO call `Alpine.start()` inside `Fliplet().then(...)`. - DO resolve authenticated URLs into a reactive `x-data` field before binding. - DO pair Alpine with History API routing from `Fliplet.Router.getBasePath()`. - DON'T bind a Promise directly into an attribute (`:src`, `:href`). - DON'T use hash-based navigation — rejected by lint. ## Related - [V3 app bootstrap](../app-bootstrap) - [V3 routing](../routing) - [V3 framework overview](overview) --- # V3 framework guide — picking and setup URL: https://developers.fliplet.com/API/v3/frameworks/overview.html # V3 framework guide — picking and setup V3 apps run **without a build step**. Source files are uploaded to the media library and fetched at runtime via `Fliplet.Media.getContents`. Dependencies are resolved through `Fliplet.require.lazy`, not `import`. This changes which framework features work and which quietly fail. Fetch the per-framework doc (`v3-framework-vue`, `v3-framework-react`, etc.) before scaffolding. This doc is for picking. ## The runtime constraints (apply to every framework) | Constraint | What it means | |---|---| | No transpile | JSX, TSX, and any other syntax the browser can't parse natively will throw `SyntaxError: Unexpected token`. | | No bundler | Bare ESM imports (`import x from 'vue'`) fail. Dependencies must come from `Fliplet.require.lazy(name)` or a full CDN URL added via `add_dependencies`. | | No CSS preprocessing | No CSS Modules, no Sass, no PostCSS. Styles are plain CSS inlined in `