Custom HTML forms that save to Grist documents. Every submission has a UUID from which the form can be resumed. This enables Grist-backed forms that need a more custom UI than Grist itself allows.
assets/ # Shared JS library
forms/ # Form HTML (one folder per form)
server/ # Flask API proxy
cd server
cp .env.example .env
# Edit .env with your Grist API key and doc IDs
uv run flask run -p 5005- Create forms//index.html
- Include the library: <script src="/assets/grist-form.js"></script>
- Initialize with your form ID:
const gristForm = new GristForm({ apiBase: '/api/forms', formId: '<form-id>', // optional callbacks: onPopulate, onBeforeSave });
- Add env vars:
GRIST_DOC_<FORM_ID>=... GRIST_TABLE_<FORM_ID>=...
GET /api/forms/<id>/record?uuid=...- fetch recordPOST /api/forms/<id>/record- create/update recordGET /forms/<id>/- serve form HTMLGET /api/forms/<id>/admin/overview- admin stats (basic auth)
- URL:
/admin/<form-id>/(example:/admin/fagerh/) - Required env vars for legacy basic auth:
ADMIN_USERNAMEADMIN_PASSWORD
- Required env vars for EURES magic-link auth:
SESSION_SECRETADMIN_AUTH_MODE_EURES_BETA=magic_linkorhybridADMIN_ALLOWED_EMAILS_EURES_BETABREVO_API_KEYfor transactional emailsBREVO_FROM_EMAILfor transactional emailsBREVO_FROM_NAMEoptional, defaults toEURES beta
- Optional hardening for magic-link auth:
ADMIN_MAGIC_LINK_TTL_SECONDS_EURES_BETAdefault900ADMIN_MAGIC_LINK_RATE_LIMIT_SECONDS_EURES_BETAdefault60SESSION_COOKIE_SECURE=true
- The admin page shows:
- total questionnaires,
- in progress (
saisie_terminee != true), - completed (
saisie_terminee == true), - searchable/filterable list.
- Magic-link flow:
GET /admin/eures-beta/login- submit an allowed email address
- receive a short-lived signed login link by email
- open the link to create a secure admin session
GET /admin/eures-beta/logoutcloses the session
- EURES beta exposes an extra Brevo diagnostic:
GET /api/forms/eures-beta/admin/brevo-health?check=1- checks env configuration and Brevo API reachability without sending an email
- Global deep health check:
GET /health?deep=1- returns HTTP
503when Brevo is broken or unreachable, which is suitable for a monitor or scheduled probe
EURES beta now runs as a dedicated Scalingo app, but the public domain
formulaires.inclusion.gouv.fr may still be attached to the fagerh app.
Use these environment variables to make the split explicit:
DEFAULT_HOME_FORM_ID- controls the
/redirect for the current app - set
DEFAULT_HOME_FORM_ID=eures-betaon the dedicated EURES app - set
DEFAULT_HOME_FORM_ID=fagerhon the Fagerh app
- controls the
PUBLIC_APP_BASE_URL- base URL used in generated public links and emails
EURES_PUBLIC_PROXY_ENABLED- when
true, the app proxies only the EURES beta public routes
- when
EURES_PUBLIC_PROXY_BASE_URL- upstream dedicated EURES app, for example
https://eures-beta.osc-fr1.scalingo.io
- upstream dedicated EURES app, for example
Recommended setup:
- on
eures-betaDEFAULT_HOME_FORM_ID=eures-betaPUBLIC_APP_BASE_URL=https://formulaires.inclusion.gouv.fr
- on
fagerhDEFAULT_HOME_FORM_ID=fagerhEURES_PUBLIC_PROXY_ENABLED=trueEURES_PUBLIC_PROXY_BASE_URL=https://eures-beta.osc-fr1.scalingo.io
Public routes proxied by fagerh when EURES proxying is enabled:
/forms/eures-beta/.../admin/eures-beta/api/forms/eures-beta/.../eures-beta/matching-feedback
- Protect record creation route (via invitation links, auth, rate limiting, etc.)
- Server side validation with a provided model