Appearance
How ShootCal is built
- Does ShootCal store my calendar on its own servers?
- How do the native apps and the web app stay in sync?
- Does the native app go through api.shootcal.com to reach my calendar?
- Where are my provider tokens kept, and how safe are they?
- Can I use ShootCal offline?
- Why is the browser app same-origin instead of calling the API directly?
- What stops my public availability link from leaking client details?
- How does the Apple Watch get my schedule?
- Is there a build step or framework behind the web app?
ShootCal’s web workspace and native apps use the same account: your ShootCal account.
There are three pieces:
- The React web app at app.shootcal.com, which runs in any modern browser. Galleries and your Website are managed there too.
- The native Mac, iPhone, iPad, and Apple Watch apps, written in Apple's SwiftUI, and the native Android app.
- A small server at api.shootcal.com that powers the web app and a few shared features.
Here is the important part for you: ShootCal stores your schedule and studio records for you. Your bookings, sessions, clients, contracts, invoices, and deposits live in your own ShootCal account, and the core studio tools work with a ShootCal sign-in: your email and password, or a Google or Microsoft identity, which identifies your account without connecting that provider's calendar or mail. Connecting a Google or Microsoft calendar is an optional extra that syncs the schedule there. ShootCal is the photographer-friendly studio built around that account. It adds the things a plain calendar does not: golden-hour timing, session types, deposit tracking, client booking pages, contracts, professional invoicing, and an availability link.
Because every surface reads and writes the same ShootCal account, everything stays in sync automatically. Mark a deposit on your computer and it shows on your phone, because both are really just reading and writing the same saved appointment.
Native offline browsing, widgets, and Watch mirroring depend on your installed app version. In versions that support them, the apps keep a local copy of your recent months so the calendar opens instantly and you can still browse it with no signal, and the Apple Watch mirrors a short list of upcoming sessions that your phone sends it; use the capabilities your compatible app version exposes, and check its store for updates. The installed web app opens without a connection and shows that you're offline.
The three surfaces and what each one is
Web app. app.shootcal.com uses React, styled with Tailwind CSS and built from Base UI components with Motion for animation, for the authenticated workspace: the calendar, agenda, tasks, clients, booking tools, contracts, questionnaires, invoices, expenses, products and services, reports, settings, the Galleries and Website pages, and shared navigation. The interface is delivered as a versioned Vite build. A deliberately retained controller layer still owns authentication, routing, API orchestration, and safety-sensitive operations while the React components own the visible interface. The browser never holds a provider token; it talks only to the server through same-origin /api requests.
Native apps. The Mac, iPhone, iPad, and Apple Watch apps use Apple's SwiftUI. The Android app has its own native interface. Start studio setup on the website, then sign in to a supported native app with the same ShootCal account. Booking configuration, provider connections, and other web settings stay on the website. Installed app features and offline behavior depend on the version shown in its store listing.
Desktop options. The native ShootCal app for Mac is available from the App Store. The complete web workspace also installs from supported browsers into its own window on Mac or Windows. Windows uses the web app.
Backend. api.shootcal.com runs the PHP backend and its account database. Optional integrations use external services for calendar sync, payments, email delivery, and AI features. It has two halves: the original native availability-feed API (Sign in with Apple) and the much larger web app API that powers the browser workspace, including the appointment store. It holds your ShootCal appointments and business records, and it keeps any connected provider token encrypted at rest.
Where the data actually lives
This is the key architectural fact: the appointments, clients, and paperwork are stored by ShootCal, in your account.
- Sessions / bookings are ShootCal appointments saved in the backend database. Each one holds its schedule (title, start and end, all-day flag, location, status) and its business details (session type, client name and email, internal notes, and the paid/unpaid deposit marker). Deposit status is a flag on the appointment, never an amount.
- Clients: the client details on your bookings, contracts, and invoices are stored with those ShootCal records. With a Google connection, you can explicitly import older clients once from your Google Contacts (the "ShootCal Clients" label) into your saved ShootCal list; the saved roster does not continuously sync with Google.
- To-dos / follow-ups are saved in your ShootCal account. An earlier Google task list can be copied into ShootCal with an explicit, one-time import; ShootCal does not keep Google Tasks or Microsoft To Do in sync.
- Optional provider copy: when you connect Google or Microsoft (one provider per account), only an appointment you explicitly assign to a selected connected calendar gets a copy of its ordinary schedule fields there; untargeted appointments stay in ShootCal only. Connected calendars can also supply busy-time conflict checks. Structured business data (client records, internal notes, contracts, invoices, and payments) stays in ShootCal and is not part of the copy, but text you enter into a calendar title, description, or location can be present in that calendar. The copy is not a backup of your ShootCal business records.
The backend's database stores all of this scoped to your account: your account, your appointments, your settings, your availability-feed configuration, your booking pages and incoming booking requests, your contracts and packages, and any invoices and manual-payment records you choose to create, plus reviews, push subscriptions, and bug reports. If a provider is connected, it also stores the encrypted connection tokens.
How the browser app talks to the server (and never touches a provider directly)
The web app is deliberately same-origin. app.shootcal.com forwards /api/ to the api.shootcal.com backend, so the browser only ever talks to app.shootcal.com. Every request the app makes goes to that same /api path and includes your private login cookie.
The browser holds only a meaningless session token in a locked-down cookie. The server stores only a scrambled, one-way version of it. Your identity is figured out purely from that server-side login record; the browser never sends a user id or calendar owner that the server trusts. Your ShootCal password is stored only as a one-way hash. When you connect Google or Microsoft, the provider's standard secure OAuth consent screen keeps that password on the provider's pages, and the resulting connection is encrypted before it is written to disk, with the key kept in a separate file outside the public website. When the web app shows your calendar, the server reads your saved ShootCal appointments; it calls the provider only to sync a connected calendar or run conflict checks. The browser never sees a provider token. (For the full sign-in handshake details, see the privacy page.)
How the native apps talk to the server
The native apps sign in with your ShootCal account (follow the sign-in options shown by your app version) and read and write your appointments through the ShootCal API. Versions that support offline viewing keep a local copy of recent months for instant launch and offline browsing, then refresh from the server in the background.
The apps also use api.shootcal.com for the cross-platform features that live with your account:
- Settings sync: session types, home location, booking defaults. Session types and booking-page settings are edited on the web. Native apps read the saved types and let you select them on appointments; use Edit session types on the web for changes or ordering.
- Reports, contracts, and client booking data: so the app's Reports, Clients, and contract-status views show the same booking history the web app tracks.
- Booking requests inbox: so the photographer can read their booking-page config and accept or decline web booking requests from inside the app. (Setting up the booking page itself still happens on the website.)
- The availability feed (Apple-identity path). Older native Mac app versions could also push their own privacy-stripped feed to the original Apple-identity API, separate from the web feed builder.
The two paths meet at your ShootCal account. The iPhone or iPad app saving an appointment and the browser reading it are operating on the same stored data, which is why they stay in sync without any direct app-to-app channel. If you connected a calendar provider on the website, the server-side sync keeps that calendar's schedule copies up to date for every surface at once.
Sync, offline, and caching
The sync mechanism is your ShootCal account. Compatible app versions and the web app read and write the same stored appointments. A saved change appears on another signed-in surface when it next refreshes. Connecting Google or Microsoft adds an optional server-side sync that carries the schedule fields to that calendar and imports changes made there back into your ShootCal appointments; if the two sides ever disagree, the appointment shows a review and you choose which schedule to keep.
Native offline reading depends on the installed version. In versions that support offline viewing, the apps keep a local copy of your events as per-month files in a shared on-device container, organized by account and calendar. On launch those apps paint from this cache immediately, then refresh from ShootCal in the background. The cache is deliberately fault-tolerant: a torn or changed file falls back to empty for just the affected section rather than forcing a full multi-year re-fetch. In versions with widget support, the same on-device cache feeds the iOS widgets. Check your installed version for the offline depth it offers.
There is no offline write queue. Edits go straight to your ShootCal account; with no connection a create, edit, or delete fails rather than queuing. Offline support is for browsing your already-loaded schedule, not editing it.
The web app opens offline but keeps no account data offline. Its service worker keeps the last app page and the versioned build files, so an installed app can open without a connection, show that you're offline, and load your account as soon as the connection returns. Online, every load still gets the current release. The saved app page is the same file for every account and holds no account data, and the service worker never caches API responses. It also delivers Web Push session reminders. Versioned React entry files and content-hashed chunks can be cached safely because a new deployment changes their URLs.
The separate desktop download keeps a limited offline schedule. After loading online, ShootCal for Mac saves sessions for today and the next 30 days and shows that copy if it cannot connect. This is separate from the browser app’s offline page. See Privacy, data storage, and security for the fields in that copy and when it is removed.
In versions that include it, the Apple Watch is a mirror, not a client. The phone is the source of truth; it pushes a slim snapshot of up to the next 40 upcoming sessions (30-day horizon) to the watch over Apple's device-to-device connectivity. The watch stores that in its own on-device container and renders the list and complication. The watch never talks to a calendar provider.
The availability feed: the one place the server pre-builds something
Your public availability link is built from your stored ShootCal appointments. The server writes a privacy-stripped .ics file to disk, served statically at feed.shootcal.com/<token>.ics (and an iframe-able embed at api.shootcal.com/embed/<token>).
The feed builder enforces a strict privacy contract: only booked days are published (deposit-received by default, or optionally any busy appointment), and each calendar entry carries only an id, timestamps, and a busy/confirmed status. No titles, locations, descriptions, or attendees ever. A forbidden-field audit runs before anything is written and fails closed. The file is rebuilt on demand and by a scheduled job and Cloudflare-purged, so the link stays current even when no app is running. This mirrors the native app, which produces the same scrubbed feed for the original Apple-identity feed path.
What runs where, in one view
- On your devices (native, varying by installed version): the full calendar UI, golden-hour math, and the calls to the backend for appointments, cross-platform settings, reports, contracts, client data, and the booking inbox. Versions that support them also keep the offline appointment cache, widgets, and the watch snapshot sender.
- In your browser (web): the React workspace (Tailwind CSS, Base UI, and Motion), with a controller layer for authentication, routing, API coordination, and durable operations. It talks only to same-origin addresses on app.shootcal.com, receives no provider token, and opens offline without keeping account data offline.
- On the server (backend): the appointment store at the center of your account, provider OAuth and encrypted token storage, the optional calendar sync and conflict checks, cookie sessions, the cross-platform settings store, booking pages and the request inbox, contracts and their signing pages, invoices and their protected client pages, reports, the availability-feed builder, and the cron jobs (feed refresh, reminders, digests, deposit updates).
- In your connected Google or Microsoft account (optional): a schedule copy (title, times, location, description, and status) for appointments assigned to a chosen connected calendar. Appointments can remain in ShootCal only; selected connected calendars can also supply busy-time checks. Your ShootCal account remains the source of truth that ties all of the above together.
FAQ
Does ShootCal store my calendar on its own servers?
Yes, and that is the point of the design. Your sessions are ShootCal appointments stored in the backend's MariaDB database, together with your clients, booking requests, contracts, optional invoices and payment records, and settings. An appointment can remain in ShootCal only or sync its ordinary schedule fields to a chosen connected Google or Microsoft calendar. ShootCal's stored appointment remains the source of truth, and business records such as clients, tasks, contracts and payments are not copied to that calendar. The one event-derived file is the public availability feed, a deliberately privacy-stripped .ics file (booked days only, no event text) that the server generates from your appointments.
How do the native apps and the web app stay in sync?
They sync through your ShootCal account. Both the native app and the web app read and write the same stored appointments through the backend, so a change made on one surface appears on the others at the next fetch. There is no direct app-to-app sync. If you connect a calendar provider, the server keeps that calendar's schedule copies in step for all surfaces at once.
Does the native app go through api.shootcal.com to reach my calendar?
Yes. The native apps sign in with your ShootCal account (follow the sign-in options shown by your app version) and reach your appointments through the ShootCal API, along with cross-platform settings, the booking-requests inbox, and the shared reports, contracts, and client booking data. On the ShootCal web and shared-API path, connecting Google or Microsoft happens on the website, the connection tokens are kept server-side, and provider calls are made by the backend; the browser app never holds a provider token and reaches a connected calendar only through the backend. Older installed app versions may handle provider access differently; follow the behavior of your installed version and check its store for updates.
Where are my provider tokens kept, and how safe are they?
If you connect Google or Microsoft on the website, the connection is stored on the server, encrypted before it is written to disk, with the key kept in a separate file outside the public website. A database leak alone yields no usable tokens. The browser never receives a provider token; it only holds a meaningless session cookie, and even that is stored only as a scrambled, one-way version on the server. This covers the ShootCal web and shared-API path; token placement in an installed native app depends on its version. Some older versions retain their own provider tokens on the device, so follow your installed version's behavior and keep it updated. (The privacy page covers the server-side encryption in full.)
Can I use ShootCal offline?
That depends on your installed app version. In versions that support offline viewing on iPhone and iPad, the app caches appointments as per-month JSON in a shared app-group container and paints from that cache before refreshing from ShootCal, so you can browse your recent months offline; check your installed version for the offline capabilities it offers. Creating or editing events requires a connection either way, since writes go straight to your account with no offline queue. The installed web app opens without a connection and shows that you're offline, then loads your account when you're back online; it keeps no schedule data offline and never caches API responses. Native Mac offline behavior depends on the installed app version; do not rely on an offline copy for unsaved work.
Why is the browser app same-origin instead of calling the API directly?
The signed-in web app at app.shootcal.com forwards /api/ requests to the api.shootcal.com backend. The Galleries and Website pages run inside app.shootcal.com too and reach Galleries through same-origin /galleries-api/ requests; client galleries keep their own address. That lets ShootCal use a single first-party, locked-down login cookie. Your identity is resolved entirely from the server-side login record tied to your ShootCal account; the browser never sends a calendar owner the server trusts, which is what guarantees one user can never read another's data.
What stops my public availability link from leaking client details?
The feed builder publishes only booked days, and each event in the .ics carries only an id, timestamps, and a confirmed status. A fixed forbidden-field list (title, location, description, attendees, and more) is audited before the file is written, and the build fails closed if any forbidden line is present. Event titles, locations, and attendees can never appear in the feed.
How does the Apple Watch get my schedule?
From your iPhone, not from any calendar provider, in versions that include Watch support. The phone pushes a slim snapshot of up to your next 40 upcoming sessions (within 30 days) to the watch over Apple's device-to-device connectivity. The watch stores it in its own on-device container and renders the list and complication. The watch app never authenticates to a provider or fetches the calendar itself. Watch availability follows your installed app version; check its store for updates.
Is there a build step or framework behind the web app?
Yes. The authenticated web interface is built with React, Tailwind CSS, Base UI, Motion, and Vite. The generated entry files and lazy-loaded chunks are committed and verified against a source hash before deployment. A proven JavaScript controller layer remains behind the React interface for authentication, routing, provider coordination, and recovery-safe mutations. That separation lets the interface evolve without casually rewriting the code that protects appointment writes, emails, invoices, contracts, and other important operations. The backend remains a separate PHP application.
This page replaces the earlier React and Material UI architecture page, which described a design ShootCal no longer uses.