Multiple apps

Most FileMaker solutions will want to have multiple web viewer widgets or screens. This guide shows you how you can easily add multiple entry points to your ADT app to support this, so that you can simplify the management of your ADT codebase.

Where you do need a second app, it's a sibling directory in the same project — not a second project. Each app binds to exactly one connected file.

Why one app

  • One build, one deploy, one update. Change a shared component or upgrade a dependency once, and every screen gets it. Separate apps mean separate bundles, separate embed fields, and separate deploys to keep in sync by hand.

  • Consistent look and behavior. A shared component library keeps all your views looking like one product. Separate apps drift apart and are harder to maintain.

  • Set up the plumbing once. The initial props handshake, the user context, the generated data client and types, and the TanStack Query config get configured a single time and reused on every screen.

  • Screens can link to each other. The dashboard can navigate to the invoice editor because they share a router. Crossing between separate apps means reloading a different bundle.

Apps in the same project do share the workspace and one vendored copy of @adt/fmdapi, but each keeps its own adt.config.json, its own generated client, and its own build output.

How it loads: one bundle, many entry points

Each Web Viewer object loads its own copy of the bundle. There is no shared memory between them — a Web Viewer on the Home layout and one on the Jobs layout are independent instances of the same app.

What differs is where each instance starts. When a Web Viewer loads, it asks FileMaker for its initial props, and the reply includes a starting route. The Home layout's Web Viewer opens to /dashboard, the Jobs layout's opens to /calendar, the Billing layout's opens to /invoice/:id.

flowchart TD
  bundle["One deployed bundle
(embedded in the file)"] bundle --> home["Home layout
Web Viewer"] bundle --> jobs["Jobs layout
Web Viewer"] bundle --> billing["Billing layout
Web Viewer"] home -->|"initial route"| r1["/dashboard"] jobs -->|"initial route"| r2["/calendar"] billing -->|"initial route"| r3["/invoice/:id"]

Each Web Viewer still feels like a single-purpose widget on its layout. Underneath, all three are the same app opened to a different route. See Initial Props for the code that reads the starting route and hands it to the router.

The entry point is a property of where the widget lives, not of the current record. The Jobs layout's Web Viewer always opens the calendar. If you also need to route by record context — open a specific customer, jump to a selected portal row — the same initial-props script decides that too.

One screen or many

A single Web Viewer can also host the whole app and let the user navigate between screens inside it, with a nav sidebar and in-app routing. FileMaker becomes the shell, and the app behaves like a normal single-page app.

Both patterns use the same bundle. Use a deep-linked Web Viewer per layout when each screen belongs to its own FileMaker context; use one full-app Web Viewer when the user should move between screens without leaving the layout.

When a separate app is justified

Splitting into a second app is rare. Two cases justify it:

  • A heavy, unique dependency. Web Viewer apps can't use code splitting, so every screen's code ships in one bundle that each Web Viewer loads up front. A screen that pulls in a large library — 3D rendering, video editing, a heavy data grid — adds that weight to every other screen's load. Pull that one screen into its own app so the rest stay light.

  • An independent release cadence. A screen that ships on its own schedule, or is owned by a different team, can warrant its own app so its deploys don't touch the shared app.

Outside these cases, add the screen as a route.

Add a second app

Ask your agent, from anywhere inside your project:

Copy
Add a second web viewer app called reports to this project.

ADT needs to know which connected file to bind to (skipped if there's only one) and the app name. Before writing anything, it checks that the target file has ADT's components installed — and provisions them itself if they're absent.

The new app lands beside the existing one and reuses the workspace root and the vendored ADT JavaScript library that the first app bootstrapped:

Copy
my-project/
├── vendor/@adt/fmdapi/       # shared
└── webviewer-apps/
    ├── dashboard/
    └── reports/              # new

Useful flags: --file, --no-install, --no-commit, --skip-components-check, --no-auto-connect, --force, --non-interactive. See Project Folders and Generated Files for the full structure and which commands run from where.