A service is a small backend for one site: one JavaScript file, a few routes, the hosts it may call and the secret keys it needs. It is served at /fn/{id}/… on the project's own address, so a page's script can call another system — a supplier's price list, a CRM, a warehouse — with a key the browser never sees. Services live in the console under Studio › Services, below Elements.
Service or module?#
| Module | Service | |
|---|---|---|
| Is | A product you install: reusable, versioned, in the catalog | Code you write for this site |
| Ships | Manifest, grants, element kits, schemas, settings pages, its own host | One index.js + routes + outbound hosts + secret names + plain settings |
| Lifecycle | Upload → check → review → install → update | Edit → check → publish a version → live |
| Can | Store events, schedules, checkout callbacks, webhooks, console pages, writes through its grants | Answer the site's own routes: call its hosts, read its secrets and settings, cache, keep small values, call the site's public and checkout API |
| Reach for it when | Several projects use it, it reacts to store events, it needs its own data model or console pages | One site needs a backend for a few buttons |
Services run on the platform's servers, so they come with the plans that include hosted code — the same plans as hosted modules. A project whose plan doesn't include them sees Services aren't part of your plan with a link to the plans.
The Services page#
One row per service: its name and id, the first route (+2 routes or guarded after it), the live version, the number of calls, the health and when it was last called. The health chip reads:
- OK — the last calls answered.
- Failing · n — a route's last call failed (an error, a timeout, the CPU budget used up). The Logs tab says why.
- Busy · n — calls were refused because too many ran at once for this project.
- Disabled — switched off; its address answers not found.
- Not published — created, but no version is live yet.
Under the table, the plan strip lists the limits your services run under (CPU per call and per minute, calls at a time). Who sees the page: owners and admins, and members whose develop access is at least readable; creating and publishing needs develop write access.
Creating a service#
New service asks for a name and derives the id from it (Lead form → lead-form); the id is part of the address and can't change later. Pick a starter:
- Empty — one
GET /pingroute that answers{ok: true}. - Call another API — a
GET /dataroute that calls one host with a secret key (API_KEY) and caches the answer for five minutes. - Send a form to another system — a guarded
POST /submitroute that forwards what the visitor sent to a target URL and answers ok or an error.
Create saves the service and publishes the starter as v1; the page opens on its Code tab. Or describe it to the assistant opens the Studio instead — the code assistant can write, check and publish services too (it can't set secret values).
The service page#
The title row shows the health chip, the live version (v7 live), the Enabled switch and a menu with Copy address, Open in Studio and Delete. Under it: the id, the full address (https://demo-{project}.inbs.dev/fn/{id}/) and who published the live version when. Five tabs:
Code#
The live version's code in the editor. Check runs the same check publishing does without saving anything. The Publish card takes a note and publishes the code as the next version (Publish v8): the check runs first, and a finding of level error refuses the publish — nothing is saved and the report says why. A warning (such as a secret with no value yet) is shown, and the version goes live anyway. The Check report lists the findings: errors, warnings and info lines such as which hosts a route tried to call during the dry run (calls are recorded, not sent). The drop object card is a cheat sheet of what the code can use:
await drop.fetch(url, init) |
HTTPS to the hosts listed in Settings, by exact name |
await drop.secret(name) |
one of the service's declared secrets |
drop.env |
the plain settings from Settings › Environment |
await drop.api(path, body) |
this site's public and checkout API (/api/public/*, /api/process/*) |
drop.cache.get / set |
a fast cache that may vanish |
drop.kv.get / set |
small durable values, per service |
drop.log(level, message) |
a line in the Logs tab |
A handler gets the request (method, path, params, query, body, locale, …) and returns a value (sent as JSON) or {status, headers, body}:
export default {
web: {
async prices({query}) {
const hit = await drop.cache.get('prices');
if (hit && !hit.stale) return hit.value;
const res = await drop.fetch(`${drop.env.API_BASE}/v2/prices`, {
headers: {authorization: `Bearer ${await drop.secret('PROVIDER_TOKEN')}`},
});
if (res.status !== 200) return hit ? hit.value : {status: 502, body: {error: 'upstream'}};
await drop.cache.set('prices', res.body, 10 * 60_000);
return {headers: {'cache-control': 'public, max-age=300'}, body: res.body};
},
},
};Settings#
Routes — the method and path of each route (
/district/:codetakes a parameter). The code may only answer these. Guarded runs the site's form check (bots, rate limits) before a POST reaches the code. Who may call decides who reaches the route on the public address, before the code runs:- Anyone — every website and script (routes created before this setting existed stay here);
- This site — the default for new routes: your project's own pages, the sites listed under Other sites that may call it, and servers or scripts. Another website gets 403. It stops other sites from using your service through their visitors; it is not a password — a script can still call it;
- Signed-in buyer — only with the buyer's identity (others get 401); the code acts for that buyer through
drop.api; - Private — console and API only — never on the public address (404); the Studio, the assistant and your API tokens can still run it.
Pass identity (for Anyone and This site) lets the code act for a signed-in buyer through
drop.apiwhen one is signed in; such answers are never cached.Outbound hosts — the only hosts
drop.fetchmay reach, over HTTPS. Private and local addresses are always blocked.Environment — plain
NAME = valuesettings the code reads asdrop.env.NAME. Anyone who can open the service sees them; keys and passwords belong under Secrets.Other sites that may call it — sites whose pages call the service's address from elsewhere: a static build on another host, or your own custom domain when its pages call the platform address. Each exact
https://address, up to ten, gets cross-origin access and passes This site routes. Pages served on the same address need nothing here.General — the name (the id is fixed), the Limits from your plan, and Delete service, which removes the code, every version, the stored values, the logs and the secrets.
Changes are saved with the Save button in the title row.
Secrets#
The secret names the code may read with drop.secret(name), each with a set or empty chip; a banner lists names that have no value yet. Values are write-only: an empty box keeps the stored value, a saved value is never shown again and never reaches the browser, and Clear removes it. Only owners and admins can set values; editors with develop access can add or remove names. Service secrets are stored encrypted with the project's other secrets but kept out of Settings › Advanced › Secrets — this tab is the only place to manage them.
Versions#
Every published version with its note, who published it (a person, the Studio assistant or an MCP token), when, the check verdict and its size; the live one carries a Live chip. Compare shows a version next to the one before it. Roll back makes an older version live again within seconds — nothing is deleted, and you can roll forward the same way.
Logs#
Routes — per route: calls, answers served from the cache, calls refused by Who may call (hover for the last reason — another website, or not signed in; refusals are counted, never logged), failures, refusals because the project was busy, the median and p95 time, and the last failure. Log — the individual calls, newest first, filtered to All, Failures or Warnings, kept for seven days: when, the route, the status, the time, the message, and Ran on — Site (the server that serves your site), Core (the platform, for a site that forwards its calls) or Studio (a preview in the console).
In the Studio#
The Studio's code view lists the services under services/: {id}.js is the live code and saving it publishes it (the button reads Publish); {id}.json shows the settings read-only. A check strip under the editor bar shows the last report — passed or failed, and each finding with a link to fix it (Set value opens the service's Secrets). Pages in the Studio preview can call /fn/{id}/… like the live site does; a preview's calls show up in the Logs as Studio. The document editor's preview only reads: a POST to a service is blocked there.
Every change — creating, publishing, rolling back, setting secrets, deleting — lands in the project's Activity feed with the service id and version, never code or a secret value.