v0.0.2-alpha.42
Pre-releaseReleased 17 packages at 0.0.2-alpha.42 in lockstep. Every package below ships at this version.
What's changed
Patch Changes
-
#332
80febb5Thanks @mobeenabdullah! - Block props now go through the field system: a block declares its editable props with the same field types a collection uses, and their values are validated by the same server-side pass entries get. Binding a data field to a block prop is derived from the prop type, so every compatible prop offers it without the block opting in. -
#343
a614d3dThanks @mobeenabdullah! - Collections and singles can now hold a page built from blocks. Add a field withblocks({ name: "content" }), optionally naming which registered blocks it accepts, and the whole page document is stored in one column and typed for you when you generate types. -
#346
4f39297Thanks @mobeenabdullah! - Field-level read rules now reach related rows inside components. A component's relationship fields copy whole rows out of the collection they point at, and neither the parent entity's field list nor the component's describes that collection's fields, so a field you protected there was returned inside the populated component to any caller that could read the parent. Reading a collection entry or a Single now judges those related rows by the rules of the collection they come from, for the caller making the request.This completes the read side of the redaction added for direct relationships. Write-side callers that assemble a payload without a caller are unchanged, so a mutation response still returns what it did before.
-
#347
81204afThanks @mobeenabdullah! - Outbox event recording is now endpoint-gated. A content write records a webhook event only when the install has at least one enabled webhook endpoint, or when the newwebhooks.auditoption is turned on. Installs with no webhooks configured no longer pay an event-table insert and a full-document serialization on every write.Recording resumes immediately when an endpoint is created in the same process, and within about 30 seconds for one created in another process. A few events may still be recorded just after the last endpoint is removed; retention prunes them.
-
#344
2dec172Thanks @mobeenabdullah! - The scaffolded blog template now uses tag-based ISR: publishing or editing content in the admin refreshes the affected pages on the next request, with no rebuild and no 60-second timer. Content-template scaffolds (blog) also installnextlyand@nextlyhq/*from thealphadist-tag so they always get thenextly/runtimecache helpers the pages use. -
#339
9ab5f19Thanks @mobeenabdullah! - Content pages can now use tag-based ISR: cache a read withcachedFindand tag it withnextlyTagsfromnextly/runtime, and every content change (create, update, publish, unpublish, delete, or slug rename) busts exactly those tags so the page regenerates on the next visit — no rebuild, noforce-dynamic. Revalidation turns on automatically wherever you mount the admin route (createDynamicHandlers). A per-operationdisableRevalidateflag lets a bulk import, seed, or CLI write skip it. See the new "ISR and caching" guide, including the rule for keying a per-user read so it cannot leak across callers. -
#337
6f19e60Thanks @mobeenabdullah! - Collections and singles created in the Schema Builder now carry their cache-revalidation setting. A new "Cache revalidation" switch on the Advanced tab (on by default) lets you opt a collection or single out of busting cache tags on write, and the setting round-trips through boot, HMR,db:sync, andmigrate:createthe same way code-firstrevalidateconfig does. Existing databases pick up the new registry column when you runnextly migrate(boot warns until it is run). -
#336
d0a45d5Thanks @mobeenabdullah! - Collections and singles can now opt out of webhook recording withwebhooks: false(or{ record: false }). Form submissions opt out by default, so visitor IP address, user agent, and submission content are no longer recorded to the webhook outbox or delivered to endpoints subscribed toentry.createdor*. Existing installs: submission events recorded before this release remain in the outbox and can be pruned manually; no data is deleted automatically. -
#351
b44b1a3Thanks @mobeenabdullah! - @nextlyhq/plugin-seo now generates a sitemap of your published content and serves it at a public HTTP route under Nextly's dynamic handler (in a scaffolded app,/admin/api/plugins/@nextlyhq/plugin-seo/sitemap.xml). It lists one URL per published entry across the collections you configure, reflects publishes and edits on the next request, and leaves out drafts and any page markednoindex. Configure the site origin withbaseUrland per-entry paths withurlFor, or disable the route withsitemap: false. -
#348
c0b3796Thanks @mobeenabdullah! - Read rules that narrow by a filter are now applied in full. A stored read rule can return a filter describing which rows the caller may see, and only part of it was being applied: the first field'sequalsvalue. A rule naming two fields filtered by one of them, a rule using any other operator applied nothing at all, and a rule whose value was legitimately falsy —0,false, an empty string — also applied nothing. In each of those cases the read returned rows the rule was written to exclude, and the matching count reported them too.Filters now go through the same translation your own
whereclauses use, so every field and every supported operator binds. Owner-only rules are unaffected: a single non-empty owner id was the one shape the old path handled correctly, which is why this went unnoticed.A filter is applied only if all of it can be applied, and access filters are held to a narrower shape than the
whereclauses you write yourself. A filter may name columns on the collection (or its localized fields) and compare them with any supported operator, including the shorthand{ field: value }form. Logicaland/orgroups, dotted paths likeauthor.name, and emptyin/not_inlists are refused rather than approximated, because each of those translates to something narrower than the rule states — or, in the dotted case, to a comparison against a different column.A refused filter is reported as forbidden, and the matching count refuses identically. If you need a shape that is currently refused, the read fails closed instead of quietly returning more than the rule allows.
-
#335
8512d5dThanks @mobeenabdullah! - Field-level read rules now apply to related rows. Populating a relationship copies the whole related row into the parent entry, and a field'saccess.readwas only ever evaluated against the collection being read, never against the collection on the other end of the relationship. A field you protected on one collection was therefore returned in full to anyone who reached it through a relationship from another, at any depth. Passwords and system columns were already stripped there; this closes the same gap for the rules you write yourself.Each related row is now judged by its own collection's rules, for the caller making the request, so a relationship cannot return more than a direct read of that row would. Trusted server-side reads that pass
overrideAccessare unaffected, and secrets are still stripped for every caller regardless.If you relied on reading a protected field indirectly through a relationship, that field will now be absent: read it as the collection that owns it, with a caller its rule admits.
-
#333
ba3e8f4Thanks @mobeenabdullah! - Collection read rules now apply over the REST API. Listing, fetching and counting entries previously ignored who was asking, so a collection configured with an owner-only or role-based read rule still returned every row to any caller who could reach the endpoint — the rule only ever held on writes and inside the Direct API. Reads now evaluate the caller against the collection's stored rules, with owner-only scoping applied in the database query so pagination and totals stay correct, and a count can no longer describe rows the caller is not allowed to see.Role-based read rules are evaluated against the caller's resolved roles, and a super-admin keeps the bypass they already have everywhere else. A scoped API key is judged on its own read grant rather than on the permissions of the account that issued it, so a read-only key issued by an administrator is no longer treated as that administrator's full session.
If you configured a read rule expecting it to be enforced, this closes that gap. If instead something in your app depended on reads returning unfiltered data, it will now see only the rows its rule allows: check any integration that reads with a user session or API key against a collection whose read rule is not
public. -
#356
a24c17eThanks @mobeenabdullah! - REST reads now default to published-only. A list, get, or count request to a Draft/Published collection or single with no?status=returns only published entries; pass?status=allor?status=draftto include drafts (subject to your read access rules). Previously these reads defaulted to returning every status, which could expose drafts to any caller.An invalid
?status=value (for example a typo like?status=pubished) is now rejected with a 400 instead of being silently treated as "all", so a malformed filter can never widen a read. Trusted server-side Direct API calls are unchanged (they still see every status). The admin panel already requests every status, so editors continue to see their drafts. -
#338
1687ff1Thanks @mobeenabdullah! - Read rules on Singles now apply over the REST API. A Single's stored read rule was enforced on every update and inside the Direct API, but reading the document over HTTP skipped it entirely, so a Single you restricted to a role was still returned in full to any caller who could reach the endpoint. Reads now evaluate the caller against the rule you configured, and a Single's related rows are redacted by the field rules of the collection they come from. Relationships reached through an embedded component are not yet covered.A scoped API key is judged on its own read grant rather than on the permissions of the account that issued it, and super-admins keep the bypass they have everywhere else.
An
owner-onlyread is judged against the document itself, since a Single has no list query to fold an ownership filter into.customread rules on Singles are not enforced by this change. A custom function may return a query constraint, which a list read compiles into SQL; applying that to a single document would mean re-implementing the filter grammar, so it is left as it behaves today rather than partly applied.public,authenticated,role-basedandowner-onlyread rules are all enforced. That rule reports "allowed" for any authenticated caller and hands back the predicate a list query would have filtered by, which a Single has no list to apply, so the predicate is checked against the row instead.The standalone
nextly/api/singles-detailGET route is deliberately public and does not authenticate. A Single with no read rule stays publicly readable there, exactly as before. A Single you restrict is no longer served by that route at all, including to callers the rule would admit, because the route has no caller to evaluate. Read restricted Singles through the authenticated API instead.If you configured a read rule on a Single expecting it to be enforced, this closes that gap. If something in your app read a restricted Single over HTTP and depended on getting it, that call will now be denied: give the caller a role the rule admits, or read it through the Direct API, which is trusted by default.
-
#353
48f82a8Thanks @mobeenabdullah! - nextly now exportsbuildMetadatafromnextly/runtime: it maps a content entry's SEO field group (from@nextlyhq/plugin-seo) to a Next.jsMetadataobject, so a page'sgenerateMetadatabecomes a single call instead of a hand-written mapping. It sets the title, description, canonical, OpenGraph, Twitter card, robots (fromnoindex), and hreflang alternates, with per-call fallbacks for blank fields. Thenextdependency is type-only, so importing it never forcesnextat load. -
#349
9cab18cThanks @mobeenabdullah! - Add the first-party @nextlyhq/plugin-seo package. Register it in your config to add an SEO field group (title, description, OG image, canonical, noindex) to the collections you name. It is opt-in and framework-agnostic (no Next.js dependency), so it is safe in headless and admin-only projects.The plugin SDK now also re-exports the field-authoring factories (
text,textarea,checkbox,upload,group) and theFieldConfigtype, so plugin authors get the whole authoring surface from@nextlyhq/plugin-sdk. -
#340
3d48019Thanks @mobeenabdullah! - Fixed a dev-mode gap where setting a collection or single towebhooks: falsedid not take effect if the same config reload also hit a schema error (for example a transient database blip during introspection, or a change awaiting confirmation). The recording opt-out is now applied up front, so a newly private entity stops recording immediately even when the rest of the reload is deferred; re-enabling recording still waits for a clean schema sync. -
#341
a98cdcfThanks @mobeenabdullah! - Fixed webhook-outbox retention not running after a Single update that opts out of both recording (webhooks: false) and cache revalidation (revalidate: { disable: true }) when a post-commit hook then fails. The Single write result now carries an explicit committed-write signal, matching the collection path, so the write-path cleanup runs for every durable write on installs without a scheduled webhook drain. -
#342
f0b4fc3Thanks @mobeenabdullah! - A Single that opts out of webhook recording (webhooks: false) no longer assembles its webhook payload on update. Previously the previous/next event documents were built (reading every component subtree) before the opt-out was checked, so a scalar update to an opted-out Single still performed webhook-only component reads and could fail on a missing or stale component table. The opt-out is now resolved before any payload assembly. -
#345
38d50d0Thanks @mobeenabdullah! - Collection status webhook events now fire. Publishing an entry deliversentry.published(and the genericentry.status_changed); unpublishing deliversentry.unpublished(andentry.status_changed); any other status change deliversentry.status_changed. A create-as-published deliversentry.created+entry.published. Per-locale status changes on a localized collection are tagged with their locale. Every status event carries an explicitstatusChange: { from, to }. Only Draft/Published collections emit these, and collections that opt out of recording (webhooks: false) emit none. Previously these event types were subscribable in the admin UI but never fired.
Packages
@nextlyhq/adapter-drizzle@nextlyhq/adapter-mysql@nextlyhq/adapter-postgres@nextlyhq/adapter-sqlite@nextlyhq/admin@nextlyhq/admin-css@nextlyhq/blocks-engine@nextlyhq/plugin-form-builder@nextlyhq/plugin-page-builder@nextlyhq/plugin-sdk@nextlyhq/plugin-seo@nextlyhq/storage-s3@nextlyhq/storage-uploadthing@nextlyhq/storage-vercel-blob@nextlyhq/uicreate-nextly-appnextly