A standing appointment is one rule and a list of exceptions, and the exceptions are the work.
A recurring appointment app stores the pattern once, then stores every departure from that pattern as a record of its own. That is the whole design, and it is the design both calendar systems a phone talks to already use. Both of them also make you say, on every single edit, whether you mean this one visit or the series. Get that answer wrong and you move a client who never asked to be moved. Every property name, rule and limit below comes from Apple's EventKit reference, Google's Calendar API documentation and Apple's App Review Guidelines, read on 14 October 2026. Most people arrive here while building a booking app for business.
What follows is how a series gets described, what happens to the rest of it when one visit changes, how far ahead to write anything down, and who is allowed to take the money. The last one has a guideline number attached and most people have it backwards.
Start hereThe short version
What does a standing appointment actually store?
Three things, and only the first one is obvious. The rule: every second Tuesday at ten, for six months. The exceptions: the Tuesday they were away, the Tuesday that moved to Wednesday, the Tuesday that is now ninety minutes instead of sixty. And the anchor: which slot in the original pattern each exception replaces, so a moved visit is still recognisably the twelfth visit of the series rather than a booking that appeared out of nowhere.
Apple and Google both model it exactly like that, with different names. In EventKit an occurrence whose attributes differ from the series default is a detached instance, and Apple documents the isDetached flag as true if and only if the event is part of a repeating event and one or more of its attributes have been modified from the repeating event's default attributes. In the Calendar API an edited occurrence is an exception, and it carries an originalStartTime that Google says uniquely identifies the instance within the recurring event series even if the instance was moved to a different time.
If your own tables have no equivalent of that anchor, you cannot tell a rescheduled visit from a cancelled visit plus a new booking. That is the one difference the client cares about, because in one case they keep their standing slot and in the other they have lost it.
How does the iOS calendar API describe a repeating series?
As a pattern, not as a list of dates. Apple describes EKRecurrenceRule as a class that describes the pattern for a recurring event. You set a frequency, an interval that says how often the rule repeats over the unit of time its frequency names, and then optional filters: days of the week, days of the month, months of the year, weeks of the year, days of the year, and set positions, which Apple defines as an array of ordinal numbers that filters which recurrences to include. Last Friday of the month is therefore a frequency, a set of weekdays and a set position, not twelve rows in a table.
The end of the series is a separate object. Apple says a recurrence rule can have an end represented by an EKRecurrenceEnd, and that the end can be based on a specific date or a maximum number of occurrences. A ten session course is an occurrence count. A membership that runs to the end of the year is a date. No end at all is also valid, and that is what a standing appointment usually is. A term timetable is the same shape with a count where the standing slot has nothing, which is why a class schedule app and a recurring appointment app share most of their schema.
Two limits are worth knowing before you design around them. Apple's note on the rule class says it is currently not possible to directly modify an EKRecurrenceRule or any of its properties, and that you get the change by creating a new rule and setting the event to use it. And the recurrenceRules property on the shared calendar item class carries one flat sentence: the implementation only supports a single recurrence rule. So every weekly at ten, except the third week of the month pattern has to be one rule plus exceptions. A screen that edits a pattern in place is really a screen that replaces one.
What happens to the rest of the series when one visit changes?
Nothing, if you asked for nothing. Neither system guesses the scope. Both make you state it.
On iOS the scope is a value called EKSpan with two cases, and Apple's wording for them is short. The thisEvent case means modifications to this event instance should affect only this instance. The futureEvents case means modifications to this event instance should also affect future instances of this event. You pass one on every save and on every delete. There is no case that reaches backwards and no case for the entire series including the visits already behind you, so if a client wants a new rate applied to last month as well, that part is manual work in your own tables.
A second line in the save documentation catches people out. Apple says that when saving an event, any fields you did not modify are updated to reflect the most recent value in the database, and that if the event has been deleted from the database it is recreated as a new event. The first half is a kindness: a stale copy held in memory does not quietly push old values over the series. The second half is not. A visit someone cancelled while your screen sat open comes back from the dead when you save.
Google splits the same decision by endpoint instead of by argument. To change one occurrence you fetch that instance and send an update to the instance edit URL, which creates an exception and leaves the parent series alone. To cancel one occurrence you send the instance back with its status set to cancelled, which keeps the record and the anchor rather than deleting them. To change the series you update the parent event. Google is blunt about the mistake in between: editing instances one at a time when you meant the whole series creates lots of exceptions that clutter the calendar, slowing down access, and sends a high number of change notifications to users. A loop over fifty two Tuesdays is fifty two emails to your client.
Google Workspace, Calendar API, recurring events, read 14 October 2026
How far ahead should the app generate bookings?
Further than the booking screen shows, and nowhere near forever. The rule is the source of truth, so the only reason to write rows in advance is that something other than the booking screen has to read them: a staff rota, a capacity count, a reminder job, a deposit charge. Pick the furthest of those, add a month, and regenerate on a schedule.
The phone hands you a hard ceiling to design against. Apple's EventKit reference for the date range predicate, the one you use to fetch events between two dates, says that for performance reasons it matches only those events within a four year time span, and that if the range between your start date and end date is greater than four years it is shortened to the first four years. It is shortened silently. An open ended query against a standing appointment returns four years of visits and then stops, and nothing in the result tells you it was cut.
Google draws the line in a different place, with a single flag. Google says that by default a list of events returns single events, recurring events and exceptions, and that instances which are not exceptions are not returned at all. Set the singleEvents parameter to true and every individual instance appears in the result, but the underlying recurring events disappear from it. So one request returns either a list of rules or a list of visits and never both. A report that counts rows counts a completely different thing depending on that flag, which is how a weekly client turns into one booking a year in somebody's dashboard.
Google Workspace, Calendar API, recurring events, read 14 October 2026
Pick the pattern
What a client says, and what you store
Pick what the client asked for. The middle line is the set of EventKit properties that describe it. The bottom line is the equivalent recurrence line in the format the Calendar API takes.
EventKit properties: frequency weekly, interval 2, daysOfTheWeek Tuesday
Recurrence line: FREQ=WEEKLY;INTERVAL=2;BYDAY=TU
What goes wrong: The start date decides which Tuesday. Move the first visit and every later one moves with it, which is almost never what the client asked for.
One rule each. Neither system wants a second rule bolted on, so anything the pattern cannot say becomes an exception row.
Who takes the payment for a standing appointment?
You do, through an ordinary payment processor, and the guideline that says so is one sentence long. App Review Guideline 3.1.3(e), Goods and Services Outside of the App, read on 14 October 2026: if your app enables people to purchase physical goods or services that will be consumed outside of the app, you must use purchase methods other than in-app purchase to collect those payments, such as Apple Pay or traditional credit card entry. A haircut, a dog walk, a physiotherapy session and a cleaning slot are all consumed outside the app. Card entry is the compliant route for them, not the risky one.
The line moves the moment what you sell lives inside the app. Guideline 3.1.1 covers that case: if you want to unlock features or functionality within your app, by way of example subscriptions, in game currencies, game levels, access to premium content, or unlocking a full version, you must use in-app purchase. So a membership that buys somebody four visits a month is a real world service. A membership that also unlocks a video library or a premium booking screen has an in-app purchase inside it. If you are not sure which one you have built, write down what the customer loses the day they stop paying. If the answer is only appointments, it is not in-app purchase.
For a repeating booking the mechanics are the part people underestimate. The card has to still work in week nine. A saved payment method has to be chargeable when the customer is not holding the phone. A failed charge on a standing slot needs a written policy before it needs code: do you hold the slot, release it, or charge a card on file next week. That is a payments job rather than a scheduling one, and adding payments walks through the setup.
The three ways to store a standing appointment
One row per visit, one rule and nothing else, or a rule with exceptions hanging off it. The third column is what both calendar systems chose and it is the one to copy. A chair rental or a salon booking app reaches this decision fastest, because a standing client and a walk in have to sit in the same day view.
| What you need to do | One row per visit | Rule only | Rule plus exceptions |
|---|---|---|---|
| Moving one visit to Wednesday | Edit that row, nothing else moves | Not possible without breaking the pattern | Write one exception row |
| Changing the whole series | Loop over every future row | Edit the rule, one write | Edit the rule, the exceptions survive |
| Showing two years ahead | Only if you generated that far | Expand the rule on read | Expand, then overlay the exceptions |
| Telling a reschedule from a cancellation | No | No | Yes |
| A staff rota can query it directly | Yes | No | No |
| Row count after two years of weekly visits | About 104 per client | One per client | One, plus the visits that changed |
Most small shops end up with the third option plus a generated view on top of it, because the rota and the capacity count want rows. The generated view is a cache. If you ever find yourself editing it instead of the rule, the rule has stopped being the truth and the next change to the series will contradict something. The one rule ceiling and the no editing a rule in place note both come from the same reference page as the properties above.
Where this lands if a builder generated your app
The data model does not change because an agent wrote the screens. You still choose a scope on every edit, you still keep an anchor on every exception, and on iOS you are still the one who presses submit. App Review Guideline 4.2.6 is the reason for that last part: apps created from a commercialized template or app generation service will be rejected unless they are submitted directly by the provider of the app's content. Read 14 October 2026.
Newly is an AI app builder. You describe the app in plain English and it writes a real React Native and Expo project you own, with two way GitHub sync so the code is never trapped. It uploads iOS builds to TestFlight, publishes to Google Play internal testing and builds a standalone Android APK. It does not submit to the App Store on your behalf, for the reason above. It is $25 a month and there is no free tier.
Ask for things in this order, because the order is what saves the rework. The rule and the exception table first. Then the dialog that asks this visit or the whole series on every edit, with the answer carried through to the delete path too. Then the generation window and the job that extends it. Then payments. Ask for the screens first and you get a booking list that looks finished and has no anchor anywhere in it, which loses its first reschedule quietly, and you will not find out until a client turns up on the wrong day.
Questions people ask about recurring bookings
A slot a client keeps on a repeating pattern instead of booking each time: every other Tuesday at ten, the first Friday of the month, weekdays at seven. In software it is one rule plus the list of times that rule was broken. The rule holds the pattern and its end, if it has one. Each broken occurrence is stored separately, with a pointer back to the slot in the pattern it replaces, so a visit that moved is still the same booking rather than a cancellation and a new one.
Describe the pattern, then describe the exceptions
Write down the rule, the anchor and the two words every edit screen has to ask. Hand that over before anybody draws a calendar grid, and the reschedules, the cancellations and the rota all fall out of the same three tables.
Start building