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.
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 action | Default request | What this API returns |
|---|---|---|
| Get All Records | GET /products | bare JSON array β leave Results Key empty |
| Get One Record | GET /products/1 | a single JSON object |
| Create a Record | POST /products | the created record with a fresh numeric id β and it persists |
| Update a Record | PUT /products/1 | the updated record (PATCH works too β see Β§3) |
| Delete a Record | DELETE /products/1 | 200 |
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.
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.
| Adalo requirement / behavior | How the mock behaves |
|---|---|
| Record IDs must be numbers β no text, special characters, or UUIDs | Every record id is an integer, always. Seeded ids count from 1; created records get the next integer. |
| JSON responses only | All endpoints speak JSON with open CORS. |
| Get One must return the record | Bare 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 instead | Both 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 parameters | Open by default; flip on protected mode to require a Bearer header, Β§6. |
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.
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.
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.
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.
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.
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.