Changelog
What changed in each release, in terms of what you would notice using it.
v4.1.0
October 8, 2026you are running thisThe project overview is a customizable dashboard of widgets, one shared layout per project, edited by people with the new Manage dashboard permission.
Added4 changes
- The project overview is a customizable dashboard of widgets. Until someone edits it, a project shows a default layout of stat tiles and shortcuts, much like the overview before 4.1.
- Widgets: stat tiles (records in a schema, form submissions, schemas, members, forms, events, endpoints, media files or API keys), a recent activity feed (new records, form submissions, event runs and endpoint calls), charts of records, submissions, event runs or endpoint calls over 7, 30 or 90 days by day or week, record tables with chosen columns, sort and filters, shortcuts into the project’s sections, and Markdown notes.
- Each project has one shared layout that everyone on the project sees. People with the new Manage dashboard permission (dashboard.manage) can add, remove, move, resize and configure widgets, and reset the layout to the default; the workspace owner always can. Moving and resizing work from the keyboard as well as by dragging.
- Saving over a layout someone else changed since you opened it is refused, and you choose whether to load their version or replace it after confirming. Nothing is overwritten silently.
Changed1 change
- Roles created before 4.1, Administrator included, do not have Manage dashboard until you tick it under Roles. Until then only the owner can edit the overview.
Security1 change
- Widgets show viewers only data they can already see: every widget’s data is worked out on the server with the viewer’s own permissions, and a widget they cannot read shows no access instead of a count. API keys cannot read or change the dashboard.
v4.0.0
October 5, 2026API calls from outside the dashboard need an API key, sent in the ESC_API_KEY header, and browser calls must come from an allowed origin.
Added6 changes
- Two new permissions for API keys and roles: forms.submit (Submit forms (public website)) and auth.use (End-user auth (login/register from your site)).
- Form honeypot: a hidden _hp (or _gotcha) field that a person never fills in. When it arrives filled, the sender gets a normal 201 but nothing is stored and no automation runs. Those fields are never stored either way.
- The API keys list marks each key still sent as Authorization: Bearer, so you can see which callers to update. A key’s permissions can now be edited after it is created.
- Allowed origins: the websites whose browsers may call a project. Set them for the whole project in Settings → Allowed origins, and narrow them for one API key when you issue or edit it. An entry is * or an origin such as https://example.com, up to 50 per list. A browser call must come from an origin on every list that is set: the project’s, the key’s, and a custom endpoint’s or auth schema’s own. A list set only on a key works from a browser on its own, with no project list. Browser preflight (OPTIONS) requests are answered for every project API route; an invalid entry in any list, an endpoint’s included, is refused with 400 INVALID_ORIGIN.
- Records, schemas, media and form submissions can be called from browser code on an allowed origin, which then gets CORS headers. With no list set they answer as before, without CORS headers. Calling from your server is still the recommendation; a key that must sit in browser code should hold only the permissions it needs and carry its own allowed origins.
- The API keys page shows each key’s allowed origins and has a Website key preset (forms.submit, auth.use, endpoints.invoke). The endpoint and auth schema editors warn when the project’s allowed origins would refuse the site they name.
Changed8 changes
- Project API keys are sent in the ESC_API_KEY header. A call from outside the dashboard with no key is answered 401 with the code API_KEY_REQUIRED. The dashboard itself is unchanged and needs no key.
- Form submissions need an API key with the new forms.submit permission. Before 4.0 anyone could post to a form. Send submissions from your site’s server, so the key stays private.
- End-user auth routes (login, register, me, logout, password change and reset, email verification) need an API key with the new auth.use permission. The signed-in user’s token stays in Authorization: Bearer, with the key in ESC_API_KEY. The verification link in emails and the hosted password reset page keep working without a key.
- Every custom endpoint needs an API key with endpoints.invoke. The “anyone (public)” access mode is gone from the editor, and endpoints saved with it now need a key. Logged-in-user endpoints need the key and the user’s token.
- A browser call from an origin missing from a list that is set is refused with 403 and the code ORIGIN_NOT_ALLOWED, before anything runs. The response’s layer says which list refused it (project, api_key, endpoint or auth_schema) without revealing the list. Before 4.0 such a call still ran, and the browser only hid the answer. Calls with no Origin header, from a server or curl, are not affected.
- End-user auth routes accept browser calls only from the auth schema’s application URL; other origins get 403 ORIGIN_NOT_ALLOWED. Check that each schema’s application URL is the origin your site really runs on. The verification link in emails opens from anywhere.
- A custom endpoint’s allowed browser origins are enforced on the server: other origins get 403 ORIGIN_NOT_ALLOWED and the flow does not run. Extra response headers can no longer set access-control-allow-origin or access-control-allow-credentials, and saved ones are ignored.
- A custom endpoint’s answer to a missing or wrong API key is readable from the browser only when the project’s allowed origins admit the site. Before, any origin could read it.
Deprecated1 change
- Sending an API key as Authorization: Bearer esc_… still works for now. Responses to it carry Deprecation: true and a Warning header naming ESC_API_KEY. If a request carries both, ESC_API_KEY wins.
Security1 change
- A dashboard session can no longer be used from another website: a request carrying the session cookie from a foreign Origin, or that the browser reports as coming from another site (another workspace’s subdomain included), is refused with 403 ORIGIN_NOT_ALLOWED. Your workspace’s own origin means the same scheme, host and port, so a site on another port of the same host must be listed like any other. Responses never allow credentials, and an allowed origin is echoed back, never answered with *.
v3.4.0
October 1, 2026Projects have a user limit — 3 by default, owner included — that platform admins can raise.
Added2 changes
- Each project allows a set number of dashboard users, 3 by default including the workspace owner. Active members and pending invitations take a seat; suspended members do not. Members shows how many seats are in use.
- Platform admins can change a project’s user limit from the client page in the admin console. Each change is recorded in the audit log.
Changed1 change
- Inviting a member, or reactivating a suspended one, is refused when the project has no free seat, with the error code SEAT_LIMIT_REACHED. Existing members are never removed by the limit.
v3.3.1
September 16, 2026A media field stores the file’s URL, as v1 did, so a site reading the field gets an address it can use.
Added2 changes
- expand resolves a media field either way: a URL is matched against the library, so v1 records and records saved by the form expand alike. A URL that names no file in the library is omitted.
- Media listings and the single-file endpoint include publicUrl, the stable address written at upload, distinct from url, which may be a time-limited signed link.
Changed2 changes
- Picking a file in the record form stores its URL — the address recorded at upload — rather than its id, which is what v1 wrote and what a site reading the field directly expects. A file with no recorded address falls back to the id.
- A media field accepts a file URL or a file id, alone or in a list. Relations are still ids only.
v3.3.0
September 16, 2026Media fields expand to the file they point at, wherever they sit, and the record form’s media picker shows the pictures.
Added2 changes
- expand on a media field returns the file — id, filename, mimeType, size, directory, url, createdAt — under the record’s "expand" object, next to expanded relations. ?expand=* now covers media fields too.
- Naming a component or dynamic zone field in expand resolves the media fields inside it: the component object, or each zone instance’s data, gains its own "expand" object, so a page built from blocks gets its image URLs in the same request.
Changed1 change
- Fetching one media file when storage is not configured returns its metadata with a null url, as the listing already did, instead of an error.
Fixed1 change
- The media picker in the record form shows image thumbnails instead of a bare list of filenames, and the chosen file stays visible as a thumbnail with its name — including when you reopen a saved record, which previously showed only the tail of an id.
v3.2.2
August 26, 2026Uploaded files keep their own names, so a media URL reads like the file it points at.
Changed2 changes
- An uploaded file is stored under its own name — /blogs/social-listening/instagram-2026.png — rather than behind a generated id, which is how v1 wrote them and how a link in a post is meant to read.
- A name already in use in that folder gains a counter, hero-1.png, instead of overwriting what is there: without the generated id, two people uploading the same filename would otherwise take each other’s files away.
v3.2.1
August 24, 2026The media library opens on every project, its URLs point at files that exist, and uploads are recorded in the shape the client’s own site already reads.
Changed2 changes
- When a bucket refuses an upload, the reason it gave — wrong region, missing permission, no such bucket — is shown instead of “Something went wrong”, and the endpoint it was sent to is written to the server log.
- Uploads are recorded with the project, the original filename, the public URL and the upload time — the same fields v1 wrote — so a site reading the media collection directly picks up new uploads without any mapping.
Fixed4 changes
- Uploads to an AWS bucket outside us-east-1 work. The endpoint was pinned to whatever was typed — usually s3.amazonaws.com, which is the us-east-1 host — so every request for a bucket in another region was refused; AWS buckets are now reached at their own regional address, derived from the region in settings.
- The media library opens on projects that have two or more folders side by side. The folder grid sorted by a name the folder listing does not send, which failed in the browser before anything rendered.
- Copy URL and image previews in the media library include the bucket in the address. A public base URL naming the storage host alone produced links without it, which the storage server answers with a 404.
- The media listing sorts newest-first for migrated libraries too, instead of grouping every record whose upload time was written by an earlier version at one end.
v3.2.0
August 12, 2026Complete documentation: a guide for every module, from getting started to integrations, served on the website.
Added1 change
- Guides for every module at /docs — getting started, schemas & records (with the full query language), endpoints, events, auth schemas, forms, media, access, and integrations — cross-linked and included in the sitemap automatically.
v3.1.0
August 12, 2026A proper identity and a proper website: a real logo everywhere, a favicon, richer landing content, and search-engine plumbing.
Added3 changes
- A real logo — an E drawn as a flow pipeline — used across the dashboard, auth pages and website, with a matching favicon.
- The landing page answers the questions people actually ask — ownership, stack requirements, what happens if you leave — in a proper FAQ.
- Search-engine plumbing: sitemap, robots rules, canonical URLs, social-share metadata and structured data for the product and FAQ.
v3.0.1
August 12, 2026Media libraries migrated from v1 get their folders back: directories embedded in old filenames are recognised everywhere folders appear.
Fixed2 changes
- Folders stored the v1 way — as a prefix inside the filename, like blog/photo.jpg — now appear in the media library’s folder list, their files are filed under them instead of flooding the top level, and filenames display without the prefix.
- v1’s zero-byte .directory placeholder files are treated as folder markers: they keep an empty folder visible but no longer show up as files.
v3.0.0
August 12, 2026Custom API development: events grow into multi-step flows with chained outputs and conditions, and projects can define their own request-to-response endpoints powered by the same flows.
Added15 changes
- Flow steps in events: query, create and update records, make HTTP requests whose responses feed later steps, and add conditions that stop a flow early. Each step’s output is available to later steps as {{steps.<name>.<path>}}.
- Custom endpoints: define your own API route (method + slug) whose logic is a flow and whose JSON response is a template over the flow’s outputs — callable publicly with rate limiting, with an API key, or by a logged-in end user of an auth schema.
- A visual flow editor for events and endpoints: steps on a canvas, configured in a side panel, with the run order always explicit.
- Endpoint permissions: endpoints.read/create/update/delete for management, and a separate endpoints.invoke so an API key can be allowed to call endpoints without being able to read or rewrite them.
- Endpoints can allow browser calls from your site: per-endpoint CORS origins, with preflight handled automatically.
- Endpoint responses are fully shapeable: any status, a content type (HTML, XML, CSV, plain text — not just JSON) and custom headers, enabling pages, RSS feeds and sitemaps served straight from a flow.
- Public GET endpoints can cache their responses for up to an hour — repeated calls stop hitting your database, and editing the endpoint clears its cache immediately.
- A Test run panel in the endpoint editor: run the draft flow against a sample request and see each step’s result and the rendered response before saving.
- Templates can read an array’s size — {{steps.<name>.length}} renders the number of records a step returned.
- Query steps can expand relations — “articles with each author embedded” is one step, using the same expander as the records API.
- A Transform step (JMESPath) for real multi-schema composition: filter, join and reshape the outputs of earlier steps declaratively — no user code, no side effects.
- Relation values are verified to reference records that actually exist — a typo’d or stale id is rejected with a per-field error instead of storing a dangling reference.
- Endpoints can be organised into named groups — the list renders one section per group, and the editor suggests existing group names.
- A documentation section at /docs, starting with a full Endpoints guide — the header help icon opens it, and the endpoints page links straight to its guide.
- Inline ? hints on the trickier endpoint and flow editor fields — hover or focus one to see what the field does and when to use it.
Changed2 changes
- The home page is now a real landing page — what the platform does, how it works, and a live-style picture of a flow — with signup as its closing section instead of the whole page.
- Event execution logs now record each step’s name and a preview of its output, so a failed automation shows where it stopped and what it had computed.
Security1 change
- Unique fields are now backed by real database indexes (nulls exempt), so two simultaneous writes can no longer both slip past the uniqueness check. Best-effort on legacy data that already contains duplicates.
v2.0.0
August 12, 2026A ground-up rebuild as a multi-tenant platform. Each workspace lives on its own subdomain, and every project connects to a MongoDB the client owns rather than to a shared database.
Added14 changes
- Multi-tenant workspaces: a subdomain resolves to a workspace, and each project connects to a database the client supplies and controls.
- Platform admin console — workspaces, projects, owners, statistics, and an append-only audit trail of every administrative action.
- Workspace rename, covering both the display name and the subdomain, with the change recorded in the audit trail.
- A full record query language: page, limit, search, sort, expand, fields, omit, filter[field], filter[field][operator] and exactFilter, on both the list and single-record endpoints.
- Relation expansion — expand=author or expand=* populates related records in one query per target schema.
- Generated API documentation per schema, with query parameters, response shapes, an error reference and copyable examples written against that schema’s own fields.
- Events: automations triggered manually or by record and form activity, running webhook and templated-email actions, with an execution log.
- End-user authentication schemas, so a client’s own customers can sign in to their site. CORS is restricted to the application URL rather than open.
- Media library backed by the client’s own S3-compatible bucket, with image thumbnails in the grid.
- Row selection and bulk delete on records.
- API keys with per-key permissions, stored as bcrypt hashes and shown once at creation.
- A published data-handling page at /privacy listing every collection on both sides, and what stays in the client’s own database.
- Version reporting on the project overview, the admin overview, the sidebar and /api/health.
- Unsaved-changes protection when leaving a half-edited record.
Changed5 changes
- Validation failures now report which field failed. The API returns per-field issues and the form shows each message beside its input instead of one sentence at the top of the page.
- Sorting uses the -field convention (sort=-createdAt). The older sort= plus order= form still works.
- Default page size is 10, previously 25. The maximum remains 100.
- The project settings page shows which database is attached, with credentials stripped, rather than an empty field.
- Required fields, uniqueness and field types are enforced server-side. In v1 they described intent rather than a guarantee.
Fixed6 changes
- Records are read from schema_<name>_records, matching v1’s layout. Migrated projects had content that the application could not see.
- Dynamic zones resolve components stored under any of v1’s three key spellings, fixing “Model "" not found” on imported data.
- URL fields accept relative paths such as /shop, which v1 permitted.
- Failed saves on the settings page are shown next to the form that failed. They were rendered in a banner far above the viewport, so a rejected save looked like nothing had happened.
- The event execution log reports why it could not load instead of doing nothing, and is indexed so it keeps working as it grows.
- Clearing the API documentation cache clears the entry that is actually being served.
Security7 changes
- Email and integration endpoints now require permission. They were reachable by any project credential, including an API key issued with no permissions — which allowed sending mail through the project’s SMTP and replacing its stored credentials.
- Administrator sign-in is rate limited per IP address and per account.
- Security headers on every response: Content-Security-Policy, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy and Permissions-Policy.
- Filter parsing rejects MongoDB operators outside a fixed allowlist, refuses field names containing $, and never searches or filters password fields.
- A workspace can no longer claim a subdomain the deployment uses for itself.
- Outbound destinations — databases, SMTP hosts, storage endpoints and webhooks — are checked against private address ranges before connecting.
- Connection strings and integration credentials are encrypted at rest and are never returned by any endpoint.