← All guides

Mock APIs for Adalo External Collections β€” a practice backend that meets every Adalo requirement

Adalo's External Collections connect an app to any REST API β€” power Lists with it, write to it with Forms and Actions. But the feature comes with hard rules, straight from Adalo's official docs (September 2026): record IDs must be numbers β€” "IDs which include text, special characters, and UUIDs" are unsupported β€” responses must be JSON, and you need a Professional, Team, or Business plan to use External Collections at all.

So before you point a $36+/month feature at a production backend β€” where Adalo's connection Test makes live requests and a misconfigured Form writes real rows β€” build the collection against a hosted mock that satisfies every rule by default. When the config works, cutover is one Base URL edit. Every curl below was verified against production before publishing.

1. Your first External Collection, in 60 seconds

The public demo project is a seeded e-commerce API. In Adalo: Database β†’ External Collections β†’ Add Collection, name it Products, and set the Base URL:

https://mockbird.mockbird.workers.dev/m/demo/products

That's the whole setup. Adalo generates its five endpoint actions from the Base URL, and this API matches every default:

Adalo actionDefault requestWhat this API returns
Get All RecordsGET /productsbare JSON array β€” leave Results Key empty
Get One RecordGET /products/1a single JSON object
Create a RecordPOST /productsthe created record with a fresh numeric id β€” and it persists
Update a RecordPUT /products/1the updated record (PATCH works too β€” see Β§3)
Delete a RecordDELETE /products/1200

Click Test in the setup modal: Adalo auto-detects the collection's Properties from the live response. Here's what it sees:

curl 'https://mockbird.mockbird.workers.dev/m/demo/products?limit=3'
# β†’ [{"id":1,"name":"World End","price":873.65, …}, …]  ids are integers

No auth parameters needed on the demo β€” a valid Test on your very first try.

2. Your own collection β€” the docs' "Trips" example, but real

Adalo's own walkthrough names its example collection Trips. One curl builds a seeded Trips API of your own (no signup):

curl -X POST https://mockbird.mockbird.workers.dev/api/projects \
  -H "content-type: application/json" -d '{"name":"adalo-trips"}'
# β†’ {"id":"abc123xyz9", "adminKey":"…"}   ← save both

curl -X POST https://mockbird.mockbird.workers.dev/api/projects/abc123xyz9/resources \
  -H "x-admin-key: YOUR_ADMIN_KEY" -H "content-type: application/json" \
  -d '{"name":"trips","fields":[
        {"name":"title","type":"sentence"},{"name":"city","type":"city"},
        {"name":"price","type":"price"},{"name":"photo","type":"image"},
        {"name":"startDate","type":"futureDate"}],"seed":12}'

Base URL for the External Collection: https://mockbird.mockbird.workers.dev/m/abc123xyz9/trips (with your real project id). The seeded records have integer ids 1–12, real city names, prices, ISO dates β€” and photo URLs that actually load, so an Adalo Image component bound to it renders something. Writes from Adalo Forms persist: we verified POST β†’ new record with id 13 β†’ PUT β†’ PATCH β†’ GET shows the changes β†’ DELETE β†’ gone.

3. Adalo's requirements vs this mock

Adalo requirement / behaviorHow the mock behaves
Record IDs must be numbers β€” no text, special characters, or UUIDsEvery record id is an integer, always. Seeded ids count from 1; created records get the next integer.
JSON responses onlyAll endpoints speak JSON with open CORS.
Get One must return the recordBare object at /:id β€” no wrapper (keep envelopes off the Get One URL, see Β§5).
Update defaults to PUT; some APIs (Adalo's docs cite Airtable) need PATCH insteadBoth work: PUT replaces, PATCH merges. Practice either method switch safely.
Results Key for APIs that wrap lists (Airtable's records, Xano's items)Default is a bare array β€” no Results Key needed. To rehearse the wrapped case, see Β§5.
Authorization via Header or Query parametersOpen by default; flip on protected mode to require a Bearer header, Β§6.

4. The numeric-ID wall β€” and the import that fixes it

The most common External Collection dead-end: your actual data lives somewhere with string ids β€” Airtable's rec8aB3xYz, a UUID-keyed Postgres API β€” which Adalo flatly doesn't support. The mock's importer converts on the way in. POST your data as a db.json (or CSV, OpenAPI, HAR):

curl -X POST 'https://mockbird.mockbird.workers.dev/api/projects/import?name=adalo-import' \
  -H "content-type: application/json" \
  -d '{"venues":[{"id":"rec8aB3xYz","name":"Blue Note","city":"NYC"},
               {"id":"rec9Qq2Wk1","name":"Preservation Hall","city":"New Orleans"}]}'
# β†’ warnings: ["venues: 2 record(s) had non-integer or duplicate ids and were renumbered"]

curl 'https://mockbird.mockbird.workers.dev/m/<newProject>/venues'
# β†’ [{"id":1,"name":"Blue Note",…},{"id":2,"name":"Preservation Hall",…}]

Your exact records, now behind an Adalo-legal API. That's not just practice: it's a working bridge while you decide how the real backend will present numeric ids.

5. Results Key rehearsal β€” wrapped responses on demand

If the real API you'll eventually connect wraps its lists β€” {"records":[…]}, {"items":[…]} β€” you'll need to fill in Adalo's Results Key field. Rehearse it by adding ?mock_envelope to the Get All Records URL only:

curl 'https://mockbird.mockbird.workers.dev/m/demo/products?mock_envelope=records&limit=1'
# β†’ {"records":[{"id":1,"name":"World End",…}]}     Results Key: records

Gotcha we hit while verifying: the envelope wraps single-record GETs too (/products/1?mock_envelope=records β†’ {"records":{…}}). Adalo's Results Key applies only to Get All β€” its Get One expects a bare object β€” so put the param on the Get All URL alone, and don't set a project-wide envelope default for an Adalo-facing project.

6. Pagination β€” what Adalo actually does (nothing automatic)

Adalo doesn't auto-paginate External Collections; if the API pages its results, an Adalo list simply shows the first page β€” a long-running community pain point. The usual workaround is a page number stored in an input or counter, injected into the Get All URL with magic text. This API supports exactly that shape:

GET /m/<project>/trips?_page=2&_limit=5     # ids 6–10 of 12
# also: ?sortBy=price&order=desc, ?city=Seoul, ?q=free-text

Wire _page= to your counter's magic text and the Load More pattern works against the mock the same way it will against the real thing. (The total is in the X-Total-Count header on every list response.) Alternatively, dodge the problem in the prototype: seed fewer records than one page.

7. Authorization rehearsal β€” real 401s, real Bearer headers

Adalo authenticates External Collections with Header or Query parameters you fill in once. To practice the full flow β€” including what a wrong key looks like β€” flip your project to protected mode and mint a long-lived token:

curl -X PUT https://mockbird.mockbird.workers.dev/api/projects/<project>/settings \
  -H "x-admin-key: YOUR_ADMIN_KEY" -H "content-type: application/json" \
  -d '{"authMode":"protected"}'

# unauthenticated Get All now β†’ 401 (what Adalo's Test shows you on a bad key)

curl -X POST https://mockbird.mockbird.workers.dev/m/<project>/auth/login \
  -H "content-type: application/json" \
  -d '{"email":"maker@example.com","password":"x","expiresIn":604800}'
# β†’ {"token":"eyJ…"}   a real signed JWT, valid 7 days

In Adalo's collection setup: Add Item β†’ Header, Name Authorization, Value Bearer eyJ… β€” the exact pattern their docs demonstrate with Airtable keys. With the header set, the Test passes; remove it and you get the same 401 a real protected API would give. More in the mock JWT auth guide.

8. Error screens and slow networks, on demand

Adalo's setup modal shows you the API's error message when a Test fails β€” but production APIs rarely fail on cue. This one does, per request:

?mock_status=503     # Get All returns a 503 with a JSON error body (verified)
?mock_delay=2000     # adds ~2s latency (we measured 2.66s round-trip) β€” watch
                     # what your list screen does while waiting
?mock_seq=503,503,200  # fails twice, then recovers β€” deterministic

Point a spare External Collection at a ?mock_status=500 URL to see precisely how your Adalo screens degrade β€” before a real outage demonstrates it to your users. Full recipes: testing loading and error states.

9. Swap day, and honest notes

Related: the Bubble version of this guide, the FlutterFlow version, the WeWeb version, the Thunkable version, mock JWT auth, and testing loading and error states.

Verification: every curl on this page was run against production on 21 Sep 2026 before publishing β€” demo list with integer ids, trips resource creation with seeded cities/prices/dates, POSTβ†’PUTβ†’PATCHβ†’GETβ†’DELETE cycle on id 13, mock_envelope=records on list and the single-record wrap gotcha, _page=2&_limit=5 returning ids 6–10 with X-Total-Count: 12, mock_status=503, a measured 2.66s mock_delay=2000, protected-mode 401 β†’ 7-day-token Bearer 200, and the string-id db.json import renumbering rec8aB3xYz-style ids to 1, 2 with an explicit warning. Adalo facts β€” the numeric-ID and paid-plan requirements, the five endpoint actions and their defaults, PUT-vs-PATCH, Results Key, and Header/Query authorization β€” are from Adalo's official help docs as of September 2026; pagination behavior is as discussed in Adalo's own community forum. We don't run your Adalo app; if a step here doesn't work, that's a bug: tell us.