I.5
Choices from the web
You need this: Lists that live elsewhere: countries, products, users, offices.
A dropdown, checkbox or radio group loads its options from a URL with choicesByUrl, from a public web service or your own API. The URL can include answers and variables in curly braces, such as /api/offices?region={region}, and the list reloads when they change. The browser calls the URL directly, so a third-party service must allow cross-origin requests (CORS). If it doesn't, or it needs a secret key, call it through your own server, as /api/countries does. To send authentication headers, use settings.web.onBeforeRequestChoices.
restcountries.com retired its keyless v3.1 API, which now answers {"success": false}.
The route treats that like an outage: it logs a warning and serves shared/seed/countries.json uncached, so the log shows fallback.
Try this
Code that just ran
These regions are read from the files that served this page, the same lines the Server Integration page shows.
shared/client/choices-from-web.js · sjs:I.5.client
View on GitHub
// In the definition — no code (shared/definitions/choices-from-web.json):
// "choicesByUrl": { "url": "/api/offices?region={region}", "valueName": "id", "titleName": "name" }
// "choicesByUrl": { "url": "/api/countries", "valueName": "code", "titleName": "name" }
// {region} is replaced with the answer to "region" (or a variable), and the list reloads when it changes.
// Optional: add your auth header to every choices request
import { settings } from "survey-core";
settings.web.onBeforeRequestChoices = (_, options) => {
options.request?.setRequestHeader("Authorization", `Bearer ${token}`);
};
routes/examples/choices-from-web.php · sjs:I.5.server
View on GitHub
// GET /api/offices?region= — {region} from the form arrives as a query parameter
Route::get('/api/offices', function (Request $request) {
return DB::table('offices')->where('region', $request->query('region', ''))->orderBy('name')->get(['id', 'name']);
});
// GET /api/countries — a proxy: the browser can't call a service without CORS, your server can
Route::get('/api/countries', function () {
try {
$countries = Cache::remember('countries', now()->addDay(), fn () => Http::timeout(5)
->get('https://restcountries.com/v3.1/all', ['fields' => 'name,cca2'])->throw()->collect()
->map(fn (array $country) => ['code' => $country['cca2'], 'name' => $country['name']['common']])
->sortBy('name')->values()->all());
} catch (Throwable $e) { // down, or a different answer: serve the bundled list for 10 minutes, so requests don't each wait for the timeout
Log::warning('restcountries.com failed; serving shared/seed/countries.json', ['error' => $e->getMessage()]);
$countries = json_decode(file_get_contents(base_path('shared/seed/countries.json')), true);
Cache::put('countries', $countries, now()->addMinutes(10));
}
return response()->json($countries)->header('Cache-Control', 'public, max-age=86400');
});
Definition: shared/definitions/choices-from-web.json