Skip to content

Archive (1.0): Configuration reference ​

Archive. This is the 1.0 guide for an installation from a clone of the repository with Docker Compose (a "checkout install"). It describes 1.0 under the product's former name, form-schema-app; the product is now Fieldset. It does not apply to an instance installed with install.sh and managed with fieldset (2.0): do not run its git, docker compose, migration, restore or .env steps on such an instance; they bypass fieldset's backups, maintenance gate and rollback. For 2.0 see the current guide. To move a checkout install to 2.0, use fieldset adopt (install guide). Where this guide names the 2.0 tool fsa, read fieldset.

All settings are environment variables in .env, read through the files in config/. Every service in compose.yaml loads .env; values in .env override the defaults built into the image. After a change, recreate the containers: docker compose up -d --force-recreate app queue scheduler (the configuration is cached when a container starts).

"Default" is the value the application uses when the variable is not set. Where .env.example (development) or .env.production.example (the production template) sets a different value, the table says so. Settings marked fixed are configuration keys without an environment variable; changing them means editing config/ and rebuilding the image.

Contents:

Application ​

config/app.php

VariableDefaultMeaning
APP_NAMEform-schema-appName in mails, the browser title and the webhook User-Agent.
APP_ENVproduction (image and production template); .env.example: localproduction for real installations. Production refuses the SSO development settings. Outside local and testing, only known host names are answered (why).
APP_DEBUGfalse; .env.example: trueShows stack traces. Never true in production.
APP_KEYnoneEncryption key, base64: + 32 random bytes. Required; back it up (why).
APP_PREVIOUS_KEYSemptyComma-separated old keys, so data encrypted with them stays readable after a key change.
APP_URLhttp://localhostPublic base URL (https://…). Used for links generated in the background.
APP_LOCALEenDefault language where no user or organization language applies (de or en).
APP_FALLBACK_LOCALEenLanguage for missing translations.
APP_ALLOW_REGISTRATIONfalseLets anyone register a user and an organization at /admin. Only for a public instance.
PLATFORM_ADMIN_EMAILSemptyComma-separated emails with access to /docs/api. /horizon and the /platform panel use the operator list (php artisan app:platform-admin) and need a /platform sign-in.
APP_INVITATIONS_PER_DAY1000Member invitations per organization in 24 hours.
APP_MAINTENANCE_DRIVERfileLaravel maintenance mode driver.
BCRYPT_ROUNDS12Password hashing cost.
fixed app.timezoneUTCServer time zone. Organizations and users have their own time zones for display and schedules.
API_RATE_LIMIT240Requests per minute on /api/v1: per integration token, otherwise per signed-in user, otherwise per IP address. Each token has its own limit, separate from its user's Field App session.

Docker Compose and the image ​

VariableDefaultMeaning
COMPOSE_FILEcompose.yaml; .env.example: compose.yaml:compose.dev.yamlRead by Docker Compose. Use compose.yaml alone in production.
COMPOSE_PROFILESemptysso starts the development Keycloak (with compose.dev.yaml only).
APP_PORT8080Port number only. compose.yaml publishes the app service as 127.0.0.1:${APP_PORT}:8080, on the loopback interface, for a reverse proxy on the same host.
FORWARD_DB_PORT, FORWARD_REDIS_PORT, FORWARD_GOTENBERG_PORT, FORWARD_MAILPIT_UI_PORT, FORWARD_MAILPIT_SMTP_PORT54329, 63799, 13000, 18025, 11025Development only (compose.dev.yaml).
RUN_MIGRATIONStrue for app (set in compose.yaml)Run php artisan migrate --force --isolated when the container starts (concurrent starts take turns on a database lock).
FRANKENPHP_CONFIGunset: FrankenPHP's default of two PHP threads per CPUExtra directives for the frankenphp block of docker/app/Caddyfile, for example num_threads 4. fsa sets num_threads from the instance's memory budget.

Set in the image (docker/app/Dockerfile) and overridden by .env when present: APP_ENV=production, LOG_CHANNEL=stderr, LOG_STDERR_FORMATTER=App\Support\Logging\JsonLogFormatter, SERVER_NAME=:8080. PHP limits (docker/app/php.ini): memory_limit=512M, upload_max_filesize=25M, post_max_size=30M.

Gotenberg flags in compose.yaml: --chromium-deny-private-ips=true, --chromium-deny-public-ips=true, --chromium-disable-javascript=true, --api-body-limit=30MB, --api-timeout=60s.

Database ​

config/database.php. PostgreSQL 15 or newer is required (16 in compose.yaml).

VariableDefaultMeaning
DB_CONNECTIONpgsql (set in compose.yaml)Only PostgreSQL is supported.
DB_HOST, DB_PORTpostgres, 5432 (set in compose.yaml)The values in .env are used only outside Docker.
DB_DATABASE, DB_USERNAME, DB_PASSWORDform_schema, form_schema, secretAlso create the database and its user on the first start of the postgres service. Set a strong password.
DB_SSLMODEpreferFor an external PostgreSQL server.
DB_URLemptyAlternative connection URL.

Redis, queue, cache and sessions ​

config/database.php, config/queue.php, config/cache.php, config/session.php, config/sanctum.php, config/horizon.php

VariableDefaultMeaning
REDIS_HOST, REDIS_PORTredis, 6379 (set in compose.yaml)Valkey service.
REDIS_PASSWORDnoneThe bundled Valkey has no password and no published port.
REDIS_CLIENTphpredis; .env.example: predisBoth work in the image.
REDIS_DB, REDIS_CACHE_DB0, 1Databases for queues and cache.
QUEUE_CONNECTIONdatabase; .env.example: redisMust be redis: Horizon only works Redis queues.
CACHE_STOREdatabase; .env.example: redisCache and locks.
SESSION_DRIVERdatabaseSessions in PostgreSQL.
SESSION_LIFETIME120; .env.example: 43200Minutes. Keep 43200 (30 days, rolling) so field workers stay signed in while offline.
SESSION_ENCRYPTfalse; .env.example: trueEncrypt session data.
SESSION_SECURE_COOKIEunset; .env.example: false; production template: trueSecure cookies for plain HTTP requests too. Requests over HTTPS (directly, through a trusted proxy, or when APP_URL is https://) always get Secure session and XSRF cookies.
SESSION_SAME_SITElaxKeep lax.
SESSION_DOMAIN, SESSION_PATHnone, /Cookie scope.
SANCTUM_STATEFUL_DOMAINSlocalhost entries plus the APP_URL hostHosts whose browser requests use the session cookie (the Field App). .env.example lists only localhost; set your host name.
HORIZON_PATHhorizonPath of the Horizon dashboard.
HORIZON_PREFIX, HORIZON_NAME, HORIZON_DOMAINderived from APP_NAME, none, noneHorizon's Redis prefix and dashboard options.
HORIZON_MAX_PROCESSES10 in production, 3 in localMaximum worker processes of supervisor-1 (queue default). fsa sets it from the instance's memory budget.
HORIZON_EXPORTS_PROCESSES1Worker processes of supervisor-exports (queue REPORTS_EXPORT_QUEUE, default exports). Keep 1 so large exports run one after another (fsa always sets 1).
fixed Horizon supervisorssupervisor-1 and supervisor-exports (config/horizon.php)Only their process counts are configurable. Queues without a supervisor are never worked.

Logging and error reporting ​

config/logging.php

VariableDefaultMeaning
LOG_CHANNELstderr (image); both templates: stackstack sends logs to the channels in LOG_STACK. stderr writes to stderr with LOG_STDERR_FORMATTER.
LOG_STACKsingle; production template: jsonChannels of stack, comma-separated. json: one JSON object per line on stderr. single: a file inside the container, lost when it is recreated. Add sentry for error reporting, for example json,sentry.
LOG_LEVELdebug; production template: infoinfo or warning in production.
LOG_STDERR_FORMATTERApp\Support\Logging\JsonLogFormatter (image)Formatter of the stderr channel.
LOG_JSON_STREAMphp://stderrWhere the json channel writes.
LOG_DEPRECATIONS_CHANNELnullPHP deprecation warnings.
SENTRY_DSNemptyDSN of a Sentry-compatible service (Sentry, GlitchTip and others), used by the sentry log channel: add sentry to LOG_STACK (with LOG_CHANNEL=stack). Without a valid DSN the channel discards everything.
SENTRY_LEVELerrorLowest log level that is reported.
SENTRY_ENVIRONMENTAPP_ENVEnvironment name on each event.
SENTRY_RELEASEemptyRelease name on each event.
SENTRY_TIMEOUT2Seconds per report.

Every request gets an X-Request-Id (taken from the request or generated), which appears in the logs.

The JSON lines contain @timestamp, level, message, channel, service, environment, request_id, the context and exception traces. Passwords, tokens, secrets and credential headers are redacted, and database errors never contain the query's values. Error reports to SENTRY_DSN carry the exception, the request id and the user id, but no request data. They are sent from the app, queue and scheduler containers, so allow outbound HTTPS to the DSN host if outbound traffic is restricted.

Security headers and proxies ​

config/security.php, config/trustedproxy.php. Background: install guide, step 5.

VariableDefaultMeaning
TRUSTED_PROXIESempty (no proxy is trusted); production template: 172.16.0.0/12IP addresses or CIDR ranges, comma-separated. Only these may set X-Forwarded-For, -Proto, -Host and -Port. The template's range is the Docker bridge range, from which a reverse proxy on the host arrives at 127.0.0.1:8080. Widen it only to the range your proxy actually connects from; never trust a range that untrusted clients can reach.
TRUSTED_HOSTSemptyExtra host names, comma-separated, besides the host of APP_URL and loopback names. Other hosts get 400 unless APP_ENV is local or testing.
SECURITY_HSTS_ENABLEDempty (automatic)Empty: send Strict-Transport-Security when the request is HTTPS or APP_URL is https://. true: always. false: never.
SECURITY_HSTS_MAX_AGE31536000 (one year)Seconds.
SECURITY_HSTS_INCLUDE_SUBDOMAINS, SECURITY_HSTS_PRELOADfalse, falseAdd includeSubDomains / preload. Only if every subdomain serves HTTPS.

Admin panel sessions end after 30 minutes without activity (idle timeout) and 12 hours after the sign-in at the latest (absolute timeout). Both are set in config/security.php:

VariableDefaultMeaning
SECURITY_ADMIN_IDLE_MINUTES30Minutes without an admin request (page or Livewire update) before the admin session ends.
SECURITY_ADMIN_ABSOLUTE_HOURS12Hours after which the admin session ends, active or not.

The Field App session is not affected: it keeps SESSION_LIFETIME (30 days, rolling). The browser has one session, so someone who returns to /admin after a timeout is signed out of that browser completely and signs in again.

Fixed: the Content Security Policy per area (admin and /platform, Field App, builder preview, API, Horizon, API documentation), X-Content-Type-Options: nosniff, Referrer-Policy: same-origin, X-Frame-Options, a Permissions-Policy that allows only camera and location, and only for the Field App. The admin pages load no scripts, styles, fonts or images from other sites.

Mail ​

config/mail.php. The instance-wide transport; organizations can add their own SMTP server in the dashboard.

VariableDefaultMeaning
MAIL_MAILERlog; .env.example: smtpsmtp in production. The image includes the smtp, sendmail and log transports only.
MAIL_HOST, MAIL_PORT127.0.0.1, 2525SMTP server.
MAIL_USERNAME, MAIL_PASSWORDnoneSMTP credentials.
MAIL_SCHEMEnonesmtps or smtp; when empty, port 465 means smtps.
MAIL_REQUIRE_TLSfalsetrue: refuse a server that offers no STARTTLS before sending the credentials or any mail. fsa sets it for --smtp-encryption starttls.
MAIL_AUTO_TLStruefalse: never start STARTTLS (a relay without TLS). fsa sets it for --smtp-encryption none.
MAIL_URLnoneAlternative DSN.
MAIL_EHLO_DOMAINhost of APP_URLName in the SMTP greeting.
MAIL_FROM_ADDRESS, MAIL_FROM_NAMEhello@example.com, APP_NAMESender of instance mail. Organizations without their own SMTP server send from this address with their own sender name.

Mail delivery rules ​

config/messaging.php

VariableDefaultMeaning
MESSAGING_QUEUEdefault queueQueue of the delivery job. A different name needs a Horizon supervisor.
MESSAGING_TRIES3Attempts per mail (30 s and 120 s apart).
MESSAGING_CUSTOMER_SENDS_PER_SUBMISSION_PER_DAY10"Send to customer" limit per submission.
MESSAGING_CUSTOMER_SENDS_PER_ORGANIZATION_PER_HOUR200"Send to customer" limit per organization.
MESSAGING_ATTACHMENT_MAX_BYTES10485760 (10 MB)Larger PDFs are sent as an expiring link.
MESSAGING_LINK_TTL_DAYS14Lifetime of document links; expired links answer 410 Gone.
MESSAGING_LINK_RATE_LIMIT30Requests per minute and IP on document links.
MESSAGING_LINK_PRUNE_AFTER_DAYS90Expired links are deleted this many days after expiry.
MESSAGING_SMTP_ALLOW_PRIVATE_HOSTSfalseAllow organization SMTP servers on private addresses and any port. Development only.
MESSAGING_SMTP_TIMEOUT15Seconds, organization SMTP servers.
fixed allowed organization SMTP ports25, 465, 587, 2525

Documents and PDF rendering ​

config/documents.php, config/laravel-pdf.php

VariableDefaultMeaning
GOTENBERG_URLhttp://gotenberg:3000 (set in compose.yaml)Gotenberg API.
GOTENBERG_USERNAME, GOTENBERG_PASSWORDnoneBasic auth, if your Gotenberg requires it.
LARAVEL_PDF_DRIVERgotenbergKeep gotenberg.
DOCUMENTS_DISKlocalDisk for PDFs and logos.
DOCUMENTS_RENDER_ON_SUBMITtrue (except in tests)Create the PDF after every submit.
DOCUMENTS_QUEUEdefaultQueue of the render job. A different name needs a Horizon supervisor.
DOCUMENTS_MAX_REQUEST_BYTES26214400 (25 MB)Size budget of one render request (photos, logo and fonts are embedded). Keep it below Gotenberg's --api-body-limit (30 MB).
DOCUMENTS_PHOTO_MAX_EDGE1600Photos are scaled down to this many pixels on the long edge for print.
DOCUMENTS_LINK_TTL_MINUTES5Lifetime of the signed PDF link in the Field App.
DOCUMENTS_SWEEP_AFTER_MINUTES, DOCUMENTS_SWEEP_BATCH10, 100documents:sweep re-dispatches PDFs missing for this long, this many per run.
DOCUMENTS_RERENDER_PER_SUBMISSION_PER_MINUTE, DOCUMENTS_RERENDER_PER_USER_PER_MINUTE1, 10Limits of "PDF neu erzeugen".
DOCUMENTS_LOGO_MAX_KB, DOCUMENTS_LOGO_MAX_SIDE, DOCUMENTS_LOGO_MAX_PIXELS2048, 4000, 16000000Logo upload limits.

Fillable PDF import ​

config/forms.php

VariableDefaultMeaning
QPDF_BINARYqpdfqpdf executable (included in the image).
PDF_IMPORT_MAX_BYTES20971520 (20 MB)Largest PDF.
PDF_IMPORT_MAX_PAGES50Most pages.
PDF_IMPORT_TIMEOUT60Seconds per qpdf call.

Submissions and uploads ​

config/submissions.php

VariableDefaultMeaning
SUBMISSIONS_MAX_PAYLOAD_BYTES2097152 (2 MB)Largest JSON request body on /api.
SUBMISSIONS_IDEMPOTENCY_TTL_HOURS24How long a submit response is replayed for the same Idempotency-Key.
SUBMISSIONS_IMAGE_MAX_PIXELS, SUBMISSIONS_IMAGE_MAX_SIDE40000000, 12000Larger images are refused.
SUBMISSIONS_IMAGE_STORE_MAX_PIXELS25000000Larger accepted images are scaled down when stored.
fixed attachment size25 MBPer file.

Optional malware scanning of the photos, signatures and files uploaded with submissions, through a ClamAV daemon (clamd) that you run yourself. config/uploads.php:

VariableDefaultMeaning
UPLOADS_CLAMAV_ENABLEDfalseScan these uploads before they are stored.
UPLOADS_CLAMAV_HOST, UPLOADS_CLAMAV_PORTclamav, 3310clamd's TCP address, reachable from the app container.
UPLOADS_CLAMAV_TIMEOUT10Seconds.
UPLOADS_CLAMAV_FAIL_OPENfalsefalse: an upload is refused with 503 scanner_unavailable when clamd gives no answer; the Field App tries again later. true: accept it unscanned.

An infected file is refused with 422 file_infected and logged as upload.infected with the signature name. A file larger than clamd's StreamMaxLength is refused with 422 file_too_large, also with UPLOADS_CLAMAV_FAIL_OPEN=true.

Workflows and webhooks ​

config/workflows.php

VariableDefaultMeaning
WORKFLOWS_COMPLETE_WITHOUT_REVIEWtrue (except in tests)Complete a submission right after submit when no workflow manages its status.
WORKFLOWS_QUEUEdefault queueQueue of workflow steps. A different name needs a Horizon supervisor.
WORKFLOWS_STEP_TRIES, WORKFLOWS_STEP_BACKOFF_BASE5, 10Attempts per step; waits of base, base×3, base×9 … seconds.
WORKFLOWS_SWEEP_AFTER_MINUTES30Runs without progress this long get their step queued again.
WORKFLOWS_START_SWEEP_AFTER_MINUTES, WORKFLOWS_START_SWEEP_DAYS10, 7Restart of workflows whose start was lost.
WORKFLOWS_EMAILS_PER_SUBMISSION_PER_DAY, WORKFLOWS_EMAILS_PER_ORGANIZATION_PER_HOUR25, 500Caps on workflow emails; over a cap the step fails and admins are notified.
WORKFLOWS_WEBHOOK_TIMEOUT, WORKFLOWS_WEBHOOK_CONNECT_TIMEOUT10, 5Seconds.
WORKFLOWS_WEBHOOK_ALLOW_PRIVATE_HOSTSfalseAllow webhooks to private addresses and any port. Development only.
WORKFLOWS_MAX_STEPS, WORKFLOWS_MAX_DEFINITION_KB, WORKFLOWS_MAX_RECIPIENTS, WORKFLOWS_MAX_ADDRESS_FIELDS25, 64, 50, 10Limits of one workflow definition.
fixed webhook ports80, 443, 8080, 8443
fixed longest review SLA2160 hours (90 days)

Reports and exports ​

config/reports.php

VariableDefaultMeaning
REPORTS_EXPORT_DISKlocalDisk for export files.
REPORTS_EXPORT_QUEUEexportsQueue worked by supervisor-exports.
REPORTS_EXPORT_TIMEOUT85Seconds per export job; keep below the queue's retry_after (90).
REPORTS_EXPORT_KEEP_DAYS7Export files are deleted after this many days.
REPORTS_EXPORT_MAX_ROWS50000Most rows of one export.
REPORTS_PDF_ZIP_MAX_BYTES, REPORTS_PDF_ZIP_MAX_FILES209715200 (200 MB), 2000Limits of a PDF ZIP.
fixed6 visible field columns, 10,000 selected rows, 2 pending exports and 10 export requests per minute per user, 500 groups per saved report, scheduled reports at 07:00 organization time

Dashboards ​

config/dashboards.php

VariableDefaultMeaning
DASHBOARD_CACHE_TTL60Seconds a widget result is cached.
DASHBOARD_STALE_DRAFT_DAYS14A draft counts as stale after this many days without changes.

Single sign-on ​

config/sso.php

VariableDefaultMeaning
SSO_ALLOW_INSECURE_IDPfalseAllow http:// and private-network identity providers (no SSRF guard). Development and CI only; refused in production.
SSO_DNS_DRIVERdnsdns looks up TXT records; config reads them from SSO_DNS_RECORDS (development and CI only; refused in production).
SSO_DNS_RECORDSemptyJSON object {"<name>": ["<value>"]} for the config driver.
SSO_SAML_SP_CERTIFICATE, SSO_SAML_SP_PRIVATE_KEYemptyOptional PEM key pair of the SAML service provider: signs authentication requests and is published in the metadata.
fixedTXT name _form-schema-app-verification.<domain>; unverify after 3 failed daily checks; IdP timeouts 10 s (connect 5 s); metadata cache 1 h; clock skew 60 s; misconfiguration notices at most every 6 h; login flows valid 10 minutes

Audit log ​

config/audit.php. See Protect the audit log.

VariableDefaultMeaning
AUDIT_RETENTION_DAYS365audit:prune (daily at 03:15) deletes older entries. At least 30; the database refuses to delete younger entries.
AUDIT_EXPORT_SYNC_MAX_ROWS2000Larger CSV exports of the audit log run in the background and arrive as a download link.
ACTIVITYLOG_ENABLEDtrueThe audit trail. Keep it enabled.

Privacy and retention ​

config/privacy.php

VariableDefaultMeaning
PRIVACY_LEGAL_HOLD_MIN_MONTHS120 (ten years)Shortest retention period a form under legal hold may have. Check the periods that apply to you; this is not legal advice.
PRIVACY_RETENTION_BATCH500Submissions per form and run of the nightly retention job (02:30); the job continues until all due submissions are done.
PRIVACY_EXPORT_KEEP_DAYS7Personal data exports and queued audit log exports are deleted after this many days.
PRIVACY_EXPORTS_PER_HOUR10Export requests per user and hour.
PRIVACY_EXPORT_DISKlocalDisk for these exports.

Integration tokens ​

config/api_tokens.php

VariableDefaultMeaning
API_TOKEN_DEFAULT_EXPIRY_DAYS90Expiry preselected for a new token. Users can still choose 7, 30, 90, 180 or 365 days, or never.

Tokens that expired more than 24 hours ago are deleted daily (sanctum:prune-expired).

Platform operator panel ​

config/platform.php

VariableDefaultMeaning
PLATFORM_IMPERSONATION_MINUTES30Longest impersonation of a member (1 to 240).
PLATFORM_SESSION_HOURS12An operator must sign in at /platform/login again after this many hours.

Files and storage ​

config/filesystems.php

VariableDefaultMeaning
FILESYSTEM_DISKlocalDefault disk (storage/app/private).

Only local disks are supported by the image: there is no S3 adapter installed. Attachments always use the local disk. Nothing is stored on the public disk; php artisan storage:link is not needed.

Other ​

VariableDefaultMeaning
API_VERSION1.0.0Version shown in the OpenAPI document.
SCRAMBLE_DEV_TOOLSAPP_DEBUGDeveloper tools on the API documentation pages.
AUTH_PASSWORD_TIMEOUT10800Seconds before a password confirmation is asked again.
VITE_APP_NAMEAPP_NAMEBuild time only.