Skip to content

Quick Start ​

The fastest way to build on Primitive is to start from an official template. Primitive supports multiple platforms as first-class citizens — there's a web template (Vue + TypeScript + Tailwind) and an iOS template (Swift + SwiftUI). Both give you a working app in minutes: authentication, local-first data storage, real-time sync, and dev tooling. (Templates are optional — the clients are plain libraries; see Using the Client Directly.)

1. Create Your App ​

Run the installer, replacing my-app with your desired app name:

bash
npx create-primitive-app my-app

It asks which platform you're building for — web or iOS — or pass --platform web / --platform ios to skip the prompt. Building for iOS requires macOS with brew install xcodegen and Xcode 16.3 or later (see iOS Toolchain and Swift Language Mode). An Apple Developer account is only needed for physical devices, TestFlight, and the App Store — the simulator runs unsigned.

npx create-primitive-app runs primitive init under the hood — see Primitive CLI if you'd rather invoke it directly or script the setup.

The installer will:

  • Prompt you to sign in to your Primitive account (if not already authenticated)
  • Create a new app on the Primitive servers
  • Download and configure the platform's template
  • Initialize a Git repository and create an initial commit
  • Install dependencies (pnpm for web, swift package resolve for iOS)

2. Run It ​

bash
cd my-app
pnpm dev
# → http://localhost:5173
bash
cd my-app
./run-ios.sh
# regenerates the Xcode project, runs codegen (model types + generated
# server types), builds, and launches the simulator

To run on a physical iPhone or iPad, set up signing once, then ./run-ios.sh --device. You need a paired iPhone connected over USB (xcrun devicectl list devices should show it as paired); the script auto-picks the first paired device, builds with -allowProvisioningUpdates so Xcode requests provisioning profiles for you, installs via devicectl, and launches with --console so print and NSLog output stream to your terminal.

On first launch the app shows its login screen. Sign in with your real email, or set up a test user for the dev loop — an address that signs in through the same login UI with the fixed code 000000, no email round-trip on any rebuild or reinstall.

The setup runs through the Primitive CLI — install it with pnpm add -g primitive-admin and sign in with primitive login. app.toml lives in your app's synced config directory; Configuring Primitive Services covers pulling and pushing it.

bash
# One-time: add your base email to testAccountBaseEmails in app.toml and push it,
# then add a derived member
primitive config push --only app
primitive users create you+primitivetest-dev@example.com

See Test User Sign-In for how it works and its guardrails.

Congratulations! You now have a working Primitive app with:

  • Authentication with a drop-in login flow (Google sign-in, email sign-in, and Passkeys on web; email sign-in, Google and Apple sign-in, and Passkeys on iOS)
  • Local-first data storage with real-time sync
  • Server-side databases with access control
  • Blob storage for files and images
  • Built-in dev tools (Document Explorer, Test Harness, and Blob Explorer on web; the Debug Inspector on iOS)
  • CLI for managing server functions, prompts, integrations, and more

3. Push to a Remote Repository (Optional) ​

The scaffold initializes a Git repository and creates an initial commit for you. To push to a remote like GitHub:

  1. Create a new repository on GitHub (don't initialize with README, .gitignore, or license)

  2. Add the remote and push:

bash
git remote add origin https://github.com/your-username/my-app.git
git branch -M main
git push -u origin main

Setting Up Google Sign In (Optional) ​

Google OAuth is optional. The template's login shows a "Sign in with Google" button automatically once it's configured — create the OAuth client in Google Cloud Console, enter its credentials in the Admin Console, and enable the provider. The full walkthrough is in Setting Up Google OAuth.

What's in the Template? ​

Both templates share the same shape: app config, data model schemas with codegen, UI scaffolding around the platform's login flow, and agent guides for AI coding assistants.

text
my-app/
├── src/
│   ├── assets/         # Static images and assets
│   ├── components/     # Vue components (organized by area)
│   ├── components/ui/  # shadcn-vue base components
│   ├── composables/    # Vue composables
│   ├── config/         # Environment configuration
│   ├── layouts/        # Page layout components
│   ├── lib/            # Business logic (pure TypeScript)
│   ├── models/         # js-bao data models (models.toml + auto-generated *.generated.ts)
│   ├── pages/          # Route components
│   ├── router/         # Vue Router setup
│   └── tests/          # Test harness test files
├── docs/               # Agent guides for AI coding assistants
├── primitive/          # CLI project config (config.json, committed)
├── .primitive/         # Local CLI state (credentials.json, local.json — gitignored)
├── .env                # Development environment variables
├── .env.production     # Production environment variables
└── wrangler.toml       # Deployment config
text
my-app/
├── Sources/PrimitiveAppTemplate/
│   ├── PrimitiveAppTemplateApp.swift   # @main entry — owns the app state
│   ├── TemplateAppState.swift          # PrimitiveAppState subclass: client lifecycle, documents
│   ├── Views/                          # SwiftUI views (ContentView wraps AuthGateView)
│   └── Models/
│       ├── models.toml                 # Your data model schemas
│       └── Generated/                  # swift-bao-codegen output
├── docs/                               # Agent guides for AI coding assistants
├── primitive/                          # CLI project config (config.json, committed)
├── .primitive/                         # Local CLI state (credentials.json, local.json — gitignored)
├── Package.swift                       # SwiftPM manifest (pulls in PrimitiveApp)
├── primitive.json                      # App ID + server URLs — bundled and read at launch
├── project.yml                         # xcodegen source of truth — edit this, regenerate the xcodeproj
├── run-ios.sh                          # Build + launch (simulator or device)
└── fastlane/                           # TestFlight + App Store lanes

Key configuration files:

Web (Vue)iOS (SwiftUI)
App configsrc/config/envConfig.ts (API URLs, App ID)primitive.json (App ID + server URLs)
Data models (start here!)src/models/models.toml — run pnpm codegen after editingSources/…/Models/models.toml — codegen is wired into both build paths
Generated sourcescommitted — every build regenerates them, so a schema change is a diff you commit with the changesame rule, for every class: models and the typed code generated from your server configuration
Typed code from your server configurationregenerated by the same pnpm codegen (part of dev/build/test/cf-deploy)regenerated by every build via scripts/codegen.sh — ./build.sh, ./run.sh, ./run-ios.sh, ./archive.sh, the fastlane lanes and every Xcode entry point
Agent guidesdocs/docs/

On iOS, the PrimitiveApp package does the heavy lifting: PrimitiveAppState owns the JsBaoClient lifecycle (your app subclasses it, like the template's TemplateAppState), AuthGateView presents PrimitiveLoginView until the user is signed in and connected, and BaoDataLoader binds queries to SwiftUI views — the counterparts of the web template's client service, PrimitiveLogin, and useJsBaoDataLoader. appState.initialize() reads primitive.json, creates the client, and attaches the auth manager; a session persisted from a previous run signs back in automatically.

Updating the Primitive Packages ​

The iOS template tracks the Primitive Swift packages by branch, so you pick up new releases by re-resolving rather than by editing a version number:

bash
swift package update
./run-ios.sh

swift package update moves the app's Package.resolved. Xcode resolves from its own copy of that pin inside <YourApp>.xcodeproj, so the two have to be kept in step — run-ios.sh, archive.sh, and the fastlane lanes copy the app's pin over Xcode's before every build, and both build paths then compile against the same revisions. If you build from the Xcode UI instead, run bash scripts/sync-xcode-pins.sh after updating so Xcode re-resolves.

iOS Toolchain and Swift Language Mode ​

Xcode 16.3 is the floor because it ships Swift 6.1, the first release that can resolve the Swift client's swift-tools-version: 6.1 manifest; the client itself is built and tested on Xcode 26. If you're on an Xcode between the two, build once before you commit to it.

The iOS template compiles your app in the Swift 6 language mode: strict concurrency checking is complete and its diagnostics are build errors, not warnings. Both of the template's build paths are set that way — swiftLanguageModes: [.v6] in Package.swift for ./build.sh and ./run.sh, and SWIFT_VERSION: "6.0" in project.yml for the Xcode target that ./run-ios.sh builds. The Primitive Swift packages are built the same way, so their types are annotated for it. To start in Swift 5 mode instead, change both knobs — leaving them out of step means Xcode and swift build disagree about the same source file. The mode is per-target, so the Primitive packages stay in Swift 6 mode whichever you pick.

Next Steps ​

Now that your app is running:

  1. Choosing Your Data Model — Decide between documents, databases, or both
  2. Working with Documents — Local-first collaborative data
  3. Working with Databases — Server-side structured storage
  4. Authentication — Set up sign-in methods
  5. Dev Tools — Inspect data, manage files, run tests
  6. Deploying to Production — Deploy your app to production
Documentation validated against js-bao-wss-client 3.4.0 · js-bao 0.11.0 · primitive-admin 1.0.62 · primitive-app 3.1.0 — 2026-09-30