Appearance
Validation Rules
What values are valid for each field type.
How to read this doc: Rules marked Backend are enforced by Laravel FormRequest classes in
upvendo-backend(cited asfile:line). Rules marked UI-only are enforced only by a front-end validator inupvendo-backofficeand are not re-checked server-side. The backend is deliberately permissive: most item / category / location text fields are onlyrequired/nullable+stringwith no length limit, and most numbers are onlynumeric/integerwith no min/max range unless listed below. A handful of branding and online-ordering text fields do carry amax— see "Fields that do have a max length".
Text Fields
Location Name
- Backend:
required|string— no min/max length. - Source:
app/Http/Requests/BackOffice/Settings/Location/StoreLocationRequest.php:111('name' => ['required', 'string'])
Item Name
- Backend:
required|string— no min/max length, any characters. - Source:
app/Http/Requests/BackOffice/Item/StoreItemRequest.php:87('name' => ['required', 'string'])
Category Name
- Backend:
required|string(nested underdetails). - Source:
app/Http/Requests/BackOffice/Category/StoreCategoryRequest.php:42('details.name' => 'required|string')
Description (item / category / location)
- Backend:
nullable|string— optional, no max length. - Sources:
- Item:
app/Http/Requests/BackOffice/Item/StoreItemRequest.php:101('description' => 'nullable|string') - Category:
app/Http/Requests/BackOffice/Category/StoreCategoryRequest.php:43('details.description' => 'nullable|string') - Location:
app/Http/Requests/BackOffice/Settings/Location/StoreLocationRequest.php:120('description' => 'nullable|string')
- Item:
- Note: Loyalty program description is the exception —
required|string(app/Http/Requests/BackOffice/Loyalty/StoreLoyaltyRequest.php:198).
Coupon / Offer Discount Code
- Backend:
<required-or-nullable>|string+ unique-in-connection. No min length, no max length, no uppercase conversion. (nullablewhen the offer is saved as a draft, otherwiserequired.) Only present when the offermethodisCODE. - Source:
app/Http/Requests/BackOffice/Offer/StoreOfferRequest.php:55-65($rules['discount_code'] = [$necessity, 'string', new UniqueInConnectionWithModel(...)]) - UI-only: back-office caps length at 10 characters. No uppercasing.
- Source:
upvendo-backoffice/src/views/marketing/offers/components/create-offers/OfferInformation.vue:50((v) => v?.length <= 10 || ...)
- Source:
Fields that do have a max length
| Field | Rule | Source |
|---|---|---|
slogan | nullable|string|max:100 | Settings/Branding/UpdateBrandingProfileRequest.php:46 |
short_description | nullable|string|max:150 | …/UpdateBrandingProfileRequest.php:47 |
wallet_program_name | nullable|string|max:50 | …/UpdateBrandingProfileRequest.php:67 |
custom_domain | nullable|string|max:255 (+ a closure check) | StoreOnlineOrderingRequest.php:223 |
payment_methods.pos_cod_payment_code | nullable|string|max:64 | StoreOnlineOrderingRequest.php:239 |
payment_methods.mplus_invoice_payment_code | nullable|string|max:64 | StoreOnlineOrderingRequest.php:241 |
welcome_popup.message | nullable|string|max:1000 | StoreOnlineOrderingRequest.php:244 |
Advance Orders (lead time)
| Field | Rule | Source |
|---|---|---|
lead_time_days | nullable|integer|min:0|max:365 | StoreItemRequest.php:146 |
available_pickup_days | nullable|array | StoreItemRequest.php:147 |
available_pickup_days.* | required + Rule::in(Carbon::getDays()) | StoreItemRequest.php:148 |
The same pair exists on the category request.
Device Profile — menu columns
| Field | Rule | Source |
|---|---|---|
menu_item_columns | nullable|integer|in:2,3 (defaults to 2) | StoreDeviceProfileRequest.php:211, default at :64 |
Number Fields
The backend does not define ranges for most numeric settings. The values below are the only numeric constraints found in FormRequest
rules(). Fields from older revisions of this doc (prep time 1–180, delay range, idle-timeout default/max, delivery-radius default, tip default/array-count, loyalty points-per-currency / point-value, KDS timer ranges, volume range) have no matching backend rule and are not listed unless a real rule exists.
Order Capacity
All on StoreOnlineSettingsRequest:
| Field | Rule |
|---|---|
order_capacity.time_slot_duration_min | required|numeric|gt:0 (the back office restricts the choice to 5/10/15/20/30/45/60) |
order_capacity.number_of_orders_per_time_slot | nullable by default, upgraded to required|numeric|gt:0 when limit_orders_per_time_slot is set (:202) |
order_capacity.number_of_items_per_time_slot | same pattern, keyed on limit_items_per_time_slot |
order_capacity.value_threshold_per_time_slot | nullable|numeric|gt:0 |
order_capacity.allow_up_to | nullable|integer|gt:0 |
order_capacity.time_specific_rules.*.number_of_orders_per_time_slot | nullable|numeric|gt:0 |
The request also normalises "enabled but empty/invalid" states before validating: a flexible-limit or category-limit toggle that is on but has no usable values is silently turned off rather than rejected.
Price / Item Pricing
- Backend:
required|numeric+MoneyFormatrule. No min, no max, no fixed decimal count. - The
MoneyFormatrule switches on theX-Price-Formatrequest header (falling back toconfig('upvendo.pricing_format'), default'cents'):cents→ value must be an integer (cents).- anything else (legacy) → value must be numeric (float/decimal allowed).
- Sources:
app/Http/Requests/BackOffice/Item/StoreItemRequest.php:116('price' => ['required', 'numeric', new MoneyFormat]),:125('pricing.*' => ['required', 'numeric', new MoneyFormat])app/Rules/Money/MoneyFormat.php:15-29config/upvendo.php:6('pricing_format' => env('PRICING_FORMAT', 'cents'))
- Note: modifier prices are
required|numericonly — noMoneyFormat. (app/Http/Requests/BackOffice/Modifier/StoreModifierGroupRequest.php:101)
Prep Time (item)
- Backend:
prep_time_seconds=nullable|numeric— no min/max, no default. (UI may sendprep_time_minutes, which the request converts to seconds.) - Stored but not applied: item prep time is gated by
ONLINE_ORDERING_INCLUDE_ITEM_PREP_TIME(config/upvendo.php:26, defaultfalse), which is not set in production, staging or testing. - Source:
app/Http/Requests/BackOffice/Item/StoreItemRequest.php:141('prep_time_seconds' => 'nullable|numeric'); conversion at:48-54.
Average Prep Time (Location)
- Backend:
required|integer— no min/max. Defaults to20if omitted (set inprepareForValidation). - The
20is now one shared constant,Constants::DEFAULT_LOCATION_AVERAGE_PREP_TIME_MINUTES(app/Constants.php:419), and every path that creates a location stamps it: the back-office form, the MplusKassa import (app/Services/BackOffice/MplusKassaIntegrationService.php:5825), the ShopCaisse import (app/Services/BackOffice/ShopCaisse/ShopCaisseLocationFactory.php:53) and the Square import (app/Services/BackOffice/Square/SquareLocationSync.php:269), which previously wrote no value at all. - It is not a read fallback. A location document that has no
average_prep_timefield reads back as0, not20(app/RawFactories/LocationFactory.php:32) — so a location created before this was unified quotes a 0-minute prep until someone saves a value on it. - Source:
app/Http/Requests/BackOffice/Settings/Location/StoreLocationRequest.php:137('average_prep_time' => 'required|integer'); default at:56-58.
Fulfillment Delay (takeout / delivery / eat-in)
- Backend:
required|numericfor takeout and delivery (only when that fulfillment type is enabled);nullable|numericfor eat-in. No min/max on any of them. - No min means negative values are accepted, and they are meant to be: a negative delay shortens the lead time for that channel. Only the finished prep figure is clamped, to 0-1440 minutes (
app/RawModels/Location.php:2015-2017, ceiling at:60). - Source:
app/Http/Requests/BackOffice/OnlineOrdering/StoreOnlineOrderingRequest.php:256,260('takeout.delay' => 'required|numeric','delivery.delay' => 'required|numeric') and:155('eat_in.delay' => 'nullable|numeric')
Idle Timeout
- Backend:
idle_timeout_secondsandshow_warning_for_seconds=required_if:idle_timeout_type,custom|numeric|min:1. Min 1; no max, no default. - Source:
app/Http/Requests/BackOffice/OnlineOrdering/StoreOnlineOrderingRequest.php:202-203
Scheduled Orders — Days in Advance
- Backend:
required|numeric|max:365(per fulfillment type, when enabled —takeout,deliveryandeat_in). Max 365; no min. - Source:
app/Http/Requests/BackOffice/OnlineOrdering/StoreOnlineOrderingRequest.php:277,281,285('...days_in_advance' => 'required|numeric|max:365')
Scheduled Orders — Hours in Advance / Minutes Before Pickup
- Backend:
required|numeric|min:1(when custom scheduled orders enabled). Min 1; no max. - Source:
app/Http/Requests/BackOffice/OnlineOrdering/StoreOnlineOrderingRequest.php:251-252
Delivery Distance / Radius
- Backend:
delivery_region.max_distance=required|numeric|min:1(distance region type).delivery_region.radius=required|numeric(other types) — no min/max.
- Source:
app/Http/Requests/BackOffice/OnlineOrdering/StoreOnlineOrderingRequest.php:292,294
Delivery Fee — Distance Pricing
- Backend:
flat_rate_per_km=required|numeric|min:0.distance_fees.*.max_km=required|numeric|gt:0;distance_fees.*.fee=required|numeric|min:0.
- Source:
app/Http/Requests/BackOffice/OnlineOrdering/StoreOnlineOrderingRequest.php:315,317-319
Minimum Order Amount
- Backend:
required|numeric(when enabled). No min/max. - Source:
app/Http/Requests/BackOffice/OnlineOrdering/StoreOnlineOrderingRequest.php:325,329('minimum_order_amount.amount','pickup_minimum_order_amount.amount')
Tip Options (in-house dine-in)
- Backend: each tip option =
numeric|min:0|max:100(when collect-tips enabled). Range 0–100; no array-count limit; no default values. - Source:
app/Http/Requests/BackOffice/InHouseSettings/StoreInHouseSettingsRequest.php:60('collect_tips.options.*' => [$collectTipsNecessity, 'numeric', 'min:0', 'max:100'])
Loyalty — Points / Spend
- Backend:
points_earned_each_visit,points_earned_each_dollar,minimum_spend_to_earn_points,sign_up_bonus_pointsare allnullable|numeric(conditionallyrequired). No min/max ranges. - Source:
app/Http/Requests/BackOffice/Loyalty/StoreLoyaltyRequest.php:110-113,201 - Reward
discount_value(percentage) =required|numeric|gt:0|lte:100; (amount) =required|numeric|gt:0. - Source:
app/Http/Requests/BackOffice/Loyalty/StoreLoyaltyRequest.php:179-181
KDS Timers / Preparation Time / Volume
- Backend:
required|integerforpreparation_time_seconds, thefor_here/to_go/pickupcaution_time_seconds/late_time_secondspairs, andsound.volume. No min/max ranges.- The
timers.deliverybucket is the exception: it issometimes|arrayand itscaution_time_seconds/late_time_secondsarerequired_with:timers.delivery|integer, notrequired|integer. preparation_time_secondshas no validation default. On KDS-profile create it is seeded from the location'saverage_prep_timex 60, falling back toConstants::DEFAULT_KDS_PREPARATION_TIME_SECONDS= 900 (15 min) when that is absent or zero (app/Services/KitchenDisplay/KdsProfileService.php:507-519,app/Constants.php:388). It is a seed, not a live link — later edits to the location do not move a stored profile.
- The
- Source:
app/Http/Requests/KitchenDisplay/UpdateSettingsRequest.php:56,59-66,74-76,82
Offer Discount Value
- Backend:
- Percentage unit:
required|numeric|gt:0|lte:100. - Amount unit:
required|numeric|gt:0.
- Percentage unit:
- Source:
app/Http/Requests/BackOffice/Offer/StoreOfferRequest.php:177-181
Offer Usage Limits
- Backend:
limit_discount_usage_amount,minimum_purchase_amount,minimum_quantity_items,benefit_quantity,max_usage_per_orderare eachnumeric|gt:0(when the enabling flag is set). No max. - Source:
app/Http/Requests/BackOffice/Offer/StoreOfferRequest.php:163,172,174,185(and:107benefit_quantity)
Contact Fields
Email
- Backend: Laravel
emailrule.nullable|email(customer) orrequired|email(location contact). No max length, no RFC-5322 claim. - Sources:
app/Http/Requests/BackOffice/Customer/StoreCustomerRequest.php:45('email' => 'nullable|email')app/Http/Requests/BackOffice/Settings/Location/StoreLocationRequest.php:130('contact_information.email' => 'required|email')
Phone
- Backend:
regexagainstConstants::PHONE_REGEX=/^[+\d\s()-]+$/. This is a character-class check only — it allows digits,+, spaces, parentheses, and hyphens. No minimum/maximum digit count. Typicallynullable|regex:...(customer) orrequired|regex:...(location contact). - Sources:
app/Constants.php:798(public const PHONE_REGEX = "/^[+\d\s()-]+$/";)app/Http/Requests/BackOffice/Customer/StoreCustomerRequest.php:46('phone' => 'nullable|regex:'.Constants::PHONE_REGEX)app/Http/Requests/BackOffice/Settings/Location/StoreLocationRequest.php:131('contact_information.phone' => 'required|regex:'.Constants::PHONE_REGEX)
URL
- No backend FormRequest rule found for a generic merchant-facing URL field.
custom_domain(online ordering) isnullable|string|max:255plus a subdomain-must-have-≥3-parts closure (not a URL/urlrule).- Source:
app/Http/Requests/BackOffice/OnlineOrdering/StoreOnlineOrderingRequest.php:223-232
- Source:
Address Fields
Address rules differ slightly between the back-office customer address and the location address. Both are shown.
Customer Address
country— Backendrequired|string.address(line 1) — Backendrequired|string.city— Backendnullable|string(optional here).postal_code— Backendnullable|string(optional).state,company,unit_number,first_name,last_name—nullable|string.phone—nullable|regex:Constants::PHONE_REGEX.- Source:
app/Http/Requests/BackOffice/Customer/StoreCustomerAddressRequest.php:27-37
Location Address
address.line1— Backendrequired|string.address.city— Backendrequired|string.address.country— Backendrequired|string.address.postal_code— Backendnullable|string(optional).address.line2—nullable|string.- Source:
app/Http/Requests/BackOffice/Settings/Location/StoreLocationRequest.php:122-126
No country-specific postal-code format validation exists in the backend. Postal code is accepted as any string. The per-country format table from older revisions of this doc is removed (no rule enforces it).
Country
- Backend:
required|string. Noin:list, no ISO-3166 enforcement at the FormRequest level for these address fields. - Sources: customer
:27, location:116above.
Date & Time Fields
Date
- Backend:
date_format:Y-m-d(e.g. offer dates, restricted dates, date of birth). - Sources:
app/Http/Requests/BackOffice/Offer/StoreOfferRequest.php:197,202(active_dates.start_date/end_date)app/Http/Requests/BackOffice/Settings/Location/StoreLocationRequest.php:157,162,167(restricted dates)app/Http/Requests/BackOffice/Customer/StoreCustomerRequest.php:47(date_of_birth)
Time
- Backend:
date_format:H:i— 24-hourHH:MM. - Sources:
- Offer:
app/Http/Requests/BackOffice/Offer/StoreOfferRequest.php:198,203 - Loyalty bonus window:
app/Http/Requests/BackOffice/Loyalty/StoreLoyaltyRequest.php:210-211 - Order cutoffs:
app/Http/Requests/BackOffice/OnlineOrdering/StoreOnlineOrderingRequest.php:158,161,164,167
- Offer:
Business Hours Time Range
- Backend: each entry has
fromandto, bothdate_format:H:i(required when the dayis_available, otherwise nullable). - Sources:
- Location:
app/Http/Requests/BackOffice/Settings/Location/StoreLocationRequest.php:185-186(business_hours.{day}.times.*.from/.to) - Online ordering custom schedule:
app/Http/Requests/BackOffice/OnlineOrdering/StoreOnlineOrderingRequest.php:271-272
- Location:
- Note: no backend rule enforces "end must be after start" for business-hours entries.
Restricted Date
- Backend:
date_format:Y-m-d. Date-range entries requiredate_untilafter_or_equal:date_from.temporarily_closed_until(online ordering) isdate_format:Y-m-d|after_or_equal:today. - Sources:
app/Http/Requests/BackOffice/Settings/Location/StoreLocationRequest.php:157-172;app/Http/Requests/BackOffice/OnlineOrdering/StoreOnlineOrderingRequest.php:234
Select / Enum Fields
Several fields are constrained to enum values via
Rule::enum(...)rather than the literal lists in older revisions of this doc. The doc's standaloneCountry Codes/Language Codes/Currency Codes/Timezonelists are not enforced by these FormRequest rules and are omitted (noin:rule found for them in the address/contact requests).
Days of Week
- Backend:
Rule::in(Carbon::getDays())(full day names: Sunday–Saturday). - Sources: offer
active_days(StoreOfferRequest.php:196); online-ordering available days (StoreOnlineOrderingRequest.php:213,215); loyalty bonus days (StoreLoyaltyRequest.php:208).
Enum-Constrained Fields (examples)
- Item status —
Rule::enum(ItemStatuses::class)(StoreItemRequest.php:115). - Item / modifier platforms / visibility —
Rule::enum(ChannelOptions::class)(StoreItemRequest.php:127,StoreModifierGroupRequest.php:108). - Modifier pricing —
Rule::enum(PricingOptions::class)(StoreModifierGroupRequest.php:87). - Offer type / method / discount unit —
Rule::enum(OfferTypes/OfferMethods/DiscountUnits::class)(StoreOfferRequest.php:36-37,69). - Image source —
Rule::enum(ImageSources::class)(e.g.StoreItemRequest.php:102).
Array Fields
Business Hours
- Backend:
business_hours=required|array. Each day:is_availablerequired|boolean,timesarray, each time{ from, to }=date_format:H:i. - No "max 3 times per day" limit in the backend.
- Source:
app/Http/Requests/BackOffice/Settings/Location/StoreLocationRequest.php:128,175-186
Delivery Zone Postal Codes
- Backend:
delivery_region.postal_codes=required|array; eachdelivery_region.postal_codes.*=required|string. - No max-count limit (no "max 500").
- Source:
app/Http/Requests/BackOffice/OnlineOrdering/StoreOnlineOrderingRequest.php:289-290
Tip Options
- Backend (in-house dine-in):
collect_tips.options=array; each valuenumeric|min:0|max:100. No min/max item count. - Source:
app/Http/Requests/BackOffice/InHouseSettings/StoreInHouseSettingsRequest.php:59-60
Allergens / Dietary Preferences (item)
- Backend: each is
nullable|array; entriesrequired|string. No "must be from a predefined list" rule server-side (free-text strings). - Source:
app/Http/Requests/BackOffice/Item/StoreItemRequest.php:133-136
Ingredients / Dietary Supplements (item)
- Backend:
nullable|array; entriesrequired|string. - Source:
app/Http/Requests/BackOffice/Item/StoreItemRequest.php:131-132,137-138
Image Fields
Item / Category / Modifier / Online-Ordering Landing Image
- Backend:
image.file=nullable|max:10240(≈ 10 MB;maxon an uploaded file is in kilobytes).image.source=nullableenum (ImageSources). - No mime/format restriction on these
image.filefields (nomimes:rule). - Sources:
- Item:
app/Http/Requests/BackOffice/Item/StoreItemRequest.php:102-103('image.file' => 'nullable|max:10240') - Category:
app/Http/Requests/BackOffice/Category/StoreCategoryRequest.php:45 - Modifier:
app/Http/Requests/BackOffice/Modifier/StoreModifierGroupRequest.php:103('modifiers.*.image.file' => 'nullable|max:10240') - Online ordering landing page:
app/Http/Requests/BackOffice/OnlineOrdering/StoreOnlineOrderingRequest.php:211('landing_page_image.file', notimage.file)
- Item:
Recommended dimensions / min-dimension / per-image-type size differences (logo 2 MB, banner 10 MB, etc.) from older revisions are not enforced by any FormRequest and are removed. Where mime restrictions do exist they are on other uploads (e.g. guided-setup thumbnail
mimes:jpg,jpeg,png,webp|max:5120, guided-setup videomimes:mp4,...|max:102400, app bundlemimes:zip).
Color Fields
- All eight branding colour fields —
primary_color,secondary_color,primary_text_color,secondary_text_color,primary_background_color,secondary_background_color,promo_code_font_color,promo_code_background_color— arerequired|stringon update (Settings/Branding/UpdateBrandingProfileRequest.php:56-64). Required, but with no hex or format check. - The create request validates only
name(Settings/Branding/StoreBrandingProfileRequest.php). - Any hex-format validation is therefore UI-only.
Special Validations
Modifier Group — Max Selected
- Backend:
settings.max_selected=requiredonly (present when "allow select more than one" is enabled). Nomax:99or numeric range. ("Allow select more than one", "is mandatory", and "allow same modifier more than once" are eachrequired|boolean.) - Source:
app/Http/Requests/BackOffice/Modifier/StoreModifierGroupRequest.php:104-106,119-121
Offer Discount Value Cap
- Backend: percentage discounts are
gt:0|lte:100; amount discountsgt:0. - Source:
app/Http/Requests/BackOffice/Offer/StoreOfferRequest.php:177-181
Delivery Fee — Free Over Amount
- Backend: when fee type is "free over certain amount",
minimum_order_amount_for_free_delivery=required|numeric(nogt:0). - Source:
app/Http/Requests/BackOffice/OnlineOrdering/StoreOnlineOrderingRequest.php:303
Offer Usage Limits
- Backend:
limit_discount_usage_amount=numeric|gt:0(whenlimit_discount_usageis on).limit_one_usage_per_customeris abooleanflag (no per-customer numeric cap field). - Source:
app/Http/Requests/BackOffice/Offer/StoreOfferRequest.php:183-188
Error Messages
Backend validation messages are Laravel defaults unless a request defines a
messages()method (e.g.StoreLoyaltyRequest::messages(),app/Http/Requests/BackOffice/Loyalty/StoreLoyaltyRequest.php:259-294). Exact user-facing copy comes from the back-office i18n locale files, not from a fixed table — the strings below are illustrative, not authoritative.
| Validation | Typical message |
|---|---|
| Required field empty | "This field is required" |
| Invalid email | "Please enter a valid email address" |
| Invalid phone | "Please enter a valid phone number" |
| File too large | "File size must be less than {max}" |
| Invalid time range | "End time must be after start time" |