DocsPricingBlogChangelog
Examples use: Lease

Every code example, in your word.

Sign inStart building

Nouns

Open

A Noun is something your product keeps track of. Leaseclock has two: a Unit, which is an apartment, and a Lease, which ties a renter to a Unit until a date. A Noun's name becomes its URL, its screen title, its SDK method and its CLI command.

A Noun file

Each Noun is one file in your product's Noun/ folder. This is Leaseclock's Lease, in full:

Noun/Lease.mdx
---
name: Lease
fields:
  unit: { type: link, target: Unit, required: true }
  renter: { type: email, required: true }
  endsOn: { type: date, required: true, indexed: true }
  status: { type: choice, options: [{ key: active }, { key: ended }] }
verbs:
  renew:
    does: Move the end date forward
    input: { endsOn: { says: the new end date, type: date, required: true } }
    from: [active]
  end: { does: End the lease, from: [active], to: ended }
---

name and fields describe what a lease holds. verbs lists what people do to a lease, and Verbs covers each line. Text after the closing --- opens the Lease page in your docs at leaseclock.com/docs/lease.

Push it with npx saas.dev push, and leases are live in Test on every surface:

Terminal
npx saas.dev push
curl https://leaseclock.com/api/leases/4b \
  -H "Authorization: Bearer $LEASECLOCK_TOKEN"
{
  "$id": "https://leaseclock.com/api/leases/4b",
  "$type": "Lease",
  "unit": "4b",
  "renter": "dana.whitfield@example.com",
  "endsOn": "2026-11-30",
  "status": "active"
}

In the app, Priya Shah, the leasing coordinator at Northgate Rentals, opens the same lease at leaseclock.com/app/leases/4b. Your customers' agents find it with the MCP server's search tool and read it with fetch.

Names

The name line sets what every surface calls the Noun. Change it and push, and every surface uses the new name.

Wherename: Lease becomes
App"Leases" in the sidebar, at leaseclock.com/app/leases
APIleaseclock.com/api/leases, and "$type": "Lease" on every record
SDK$.Lease, with list, get, create and update, plus a method for each Verb
CLInpx leaseclock show leases and npx leaseclock do Lease.renew
MCPLease as a type in search and fetch, and Lease.renew in do
DocsA page at leaseclock.com/docs/lease

A name is singular, starts with a capital letter, and holds letters and digits only, up to 64 characters. The URL uses the plural in lower case: Lease becomes leases, and a Noun named Property becomes properties. Write a two-word name joined, as RentReview, and screens show it as "Rent review".

A name that breaks a rule returns an error with the line and the fix:

422  Lease.mdx line 2: name "lease" must start upper-case. Rename it to "Lease".

Every error code links to its fix in Errors, so you or your agent correct the file and push again.

Fields

fields lists what each record holds. A field is a key, a type and any settings the field needs:

endsOn: { type: date, required: true, indexed: true }

A key starts with a lower-case letter and joins words in camelCase. Screens label the field from its key, so endsOn shows as "Ends on". Each line of Lease's fields sets what people see and what the API returns:

LineIn the appIn the API
unit: { type: link, target: Unit, required: true }A Unit column, and a Unit picker in the form"unit": "4b", filtered with unit=4b
renter: { type: email, required: true }A mail link, and an input that accepts only an address"renter": "dana.whitfield@example.com"
endsOn: { type: date, required: true, indexed: true }A date column the list sorts by, and an "Ends before" filter"endsOn": "2026-11-30", filtered with endsOn.before=2027-01-01 and sorted with sort=endsOn
status: { type: choice, options: [...] }A badge on each row, and a tab for each option"status": "active", filtered with status=active

Settings every field takes

SettingWhat it does
required: trueThe record saves only with a value. The form marks the field, and an empty value returns a 422 naming it.
indexed: trueLists sort and filter by the field at any size. The first indexed field sets a list's default order.
labelThe name on screens and in docs, when the key's own words are wrong: label: Move-out date.
descriptionHelp text under the input, on the docs page, and in the SDK type your editor shows on hover.
defaultThe value a new record starts with: default: active.
requiredWhenRequired only when other fields match: requiredWhen: { status: ended }.

Add a field

Add one line and push. Leaseclock gains rent:

Noun/Lease.mdx
 fields:
   unit: { type: link, target: Unit, required: true }
   renter: { type: email, required: true }
   endsOn: { type: date, required: true, indexed: true }
   status: { type: choice, options: [{ key: active }, { key: ended }] }
+  rent: { type: money }

On the next request, the Leases list has a Rent column, the form has a money input, rent joins the API schema and the SDK's Lease type, and the changelog at leaseclock.com/blog/changelog reads "Leases now track rent". Existing leases have no rent until someone sets it, unless the field has a default.

Field types

The type says what a value is, and every surface reads it. A date gets a date picker, a date column and range filters, and a money field gets a currency input and totals, with nothing else written.

Text

TypeHoldsSettingsIn JSON
textOne line, such as a unit's labelmax, pattern"4B"
longTextParagraphs, such as notes on a lease. rich: true allows formatting.max, pattern, rich"Renter asked about…"
emailAn email address, checked on savepattern"dana.whitfield@example.com"
phoneA phone number, shown as a call linkpattern"+1 612 555 0142"
urlA web address, shown as a linkpattern"https://…"

pattern is a regular expression every value must match: pattern: '^[0-9]+[A-Z]$' holds a unit label to the form 4B.

Numbers

TypeHoldsSettingsIn JSON
numberA count or a measure, such as bedroomsunit, decimals, range2
moneyAn amount in a currency, such as monthly rentcurrency, decimals, range1850, which is $1,850
percentA rate, such as a late feedecimals, range5, which is 5%
autoNumberA number assigned when the record is created, counting upstart1042

Money is always an amount in the currency's main unit, the same way an Offer writes its price. A money field takes the currency of your Offer unless it sets its own, as currency: EUR. range sets the least and most a value may be: range: { min: 0 }.

Dates

TypeHoldsIn JSON
dateA day, such as when a lease ends"2026-11-30"
dateTimeA moment, stored in UTC and shown in each viewer's time zone"2026-11-30T17:00:00.000Z"
monthA month, such as the month rent is due"2026-11"

Choices

TypeHoldsSettingsIn JSON
yesNoYes or no, such as whether pets are allowedtrue
choiceOne option from a list, such as a lease's statusoptions"active"
choicesAny of the options, such as a unit's amenitiesoptions["parking", "laundry"]
classificationA standard code from NAICS, O*NET or UNSPSCclassification"531110"

Each option is a key with an optional label: { key: active, label: Active }. Without a label, screens show the key with a capital letter.

TypeHoldsSettingsIn JSON
linkAnother record, such as a lease's Unittarget, many, onDelete, reverseLabel"4b"
teamPeople or agents in the customer's workspace, such as who manages a unitmany, takes"priya-shah"
fileAn uploaded file, up to 25 MB, such as a signed leaseaccept, maxMb, many{ "id", "name", "mime", "size" }

Worked out

TypeHoldsSettingsIn JSON
lookupA field read through a link, such as a lease's buildinglookupthe source field's value
rollupA count, sum, minimum, maximum or average over linked recordsrollup3

Lookups and rollups are read-only. Each stays current with the records it reads, and a request that sets one returns a 422.

Lists

Add many: true to hold a list. phones: { type: phone, many: true } keeps every number a renter gives, and max: 3 caps it at three.

A link field points at a record of another Noun. Lease's unit points at a Unit:

Noun/Lease.mdx
unit: { type: link, target: Unit, required: true }

The link reads both ways. The Lease form picks a Unit by its label, and each Unit page lists the leases that point at it, under the name reverseLabel sets. In the API, filter leases by unit with GET /api/leases?unit=4b. In the SDK, it is $.Lease.list({ unit: '4b' }).

onDelete decides what happens to Unit 4B's leases when someone deletes Unit 4B:

onDeleteWhat happens
blockThe delete returns a 409 while any lease points at the Unit. This is the default.
clearEach lease keeps its record and loses its Unit.
cascadeThe leases are deleted with the Unit.

Every delete is recorded in the record's history, so each of these can be undone.

A lookup shows a field of the linked record. A rollup adds up over the records that link in. Add a building to each lease and a lease count to each Unit:

Noun/Lease.mdx
building: { type: lookup, lookup: { via: unit, field: building } }
Noun/Unit.mdx
leaseCount: { type: rollup, rollup: { via: Lease.unit, fn: count } }

via names the link: unit on Lease itself, or Lease.unit for the link that points in from Lease. A rollup's fn is count, sum, min, max or average, and every fn except count names the field it reads.

Ids and titles

Every record has an id, and the id is its address: Dana Whitfield's lease is 4b, at leaseclock.com/api/leases/4b. Screens title a record by its name, title or label field, or else its first text field, so Unit 4B shows as "4B" wherever a lease links to it.

  • Verbs: what people and agents do to a Noun
  • Offers: the plan your customers buy, and the limits it sets on each Noun
  • Constraints: every naming and field rule, and the surface that depends on it

Contents