0.0.2-alpha.72
- Security: the MCP server SDK, nodemailer, undici and fast-uri move past their published advisories. Apps that send email over SMTP need nodemailer 10.
- New projects ship the route that serves local uploads under
next start. - A code access rule now narrows what a role allows: it no longer lets in a signed-in user who lacks the permission.
Before you upgrade14
- 1
Run
nextly migrateafter upgrading, before the new version serves traffic.alpha.72 adds to Nextly's own tables: a new
email_queue_recipientstable and two columns onemail_queue. The command applies them but may still print "Nothing to migrate. Database is up to date."; the change is recorded innextly_schema_events.--dry-rundoesn't list it either. - 2
Security: upgrade to pick up the MCP server SDK 2.3.1, nodemailer 10.2.1, undici 7.30.1 and fast-uri 3.1.9
(#2796, #2812). With
@nextlyhq/plugin-mcpturned on, one request to its endpoint could exhaust the server's memory (GHSA-46jr-4wx8-v453). The other advisories don't reach Nextly's use of those packages; the floors rise so no install resolves an affected version. If your app sends email over SMTP, it needs nodemailer 10:npm install nodemailer@^10.2.1. - 3
Security: password fields inside field groups are now stored hashed, and no read returns a password #2785
This release doesn't rewrite stored values: if a collection or Single has a password field inside a field group, set those passwords again so they're stored hashed.
- 4
Security: publishing several languages at once asks the publish rule for each language #2557
A rule that answers by
localewas asked with none and could let a language go live. Nothing to change, unless a publish rule relied on being asked once. - 5
Security:
GET /me/capabilitiesnames only what the caller may read #2771Before, any signed-in user could learn every collection's and Single's name.
- 6
A code access rule no longer lets in a user or API key that lacks the permission #2731
The permission is asked first, and the rule only narrows it. Where a rule was letting such users in, grant the permission on the Roles screen. Each request newly refused this way is logged once, naming the permission to grant.
- 7
An entry the caller may not read is answered as missing: 404
NOT_FOUND, not 403 #2777This holds for REST, the Direct API and plugin services. A client that treated 403 as "exists but hidden" needs updating.
- 8
Removed from
nextly/auth, deprecated in 0.0.2-alpha.69 #2779:
hasRole,hasAnyRole,hasAllRoles,requireRole,requireAnyRole,requireAllRoles,checkPermission,authenticateApiKey,checkCollectionAccessand theAuthContexttype. Usenextly.access.check({ userId, resource, operation }),nextly.access.checkApiKey({ rawKey })orisSuperAdmin(userId). - 9
A rule's
permissionsno longer includes theposts:readspelling #2780A rule checking it now refuses, with no warning. Check
read-posts, as the Roles screen shows it. - 10
A field write rule's
datano longer carries the server's own values as the request sent them #2770:
idis the stored entry's on an update, andcreatedAt,updatedAt,createdByandfirstPublishedAtare gone. - 11
Optional: drop the retired
user_permission_cachetable #2695by running
nextly migratewithNEXTLY_DROP_RETIRED_AUTH_TABLES=1. TheuserPermissionCacheschema export is removed; delete any query that read it. - 12
Startup now checks
admin.dashboardand plugins' dashboard contributions(#2825,
@experimental). A config naming a widget that doesn't exist, or a key, width or setting the widget doesn't take, stops startup, naming the nearest match.admin.dashboard's keys are checked but not applied yet in this version. - 13
New projects ship the uploads route #2792
A project created before this release adds
src/app/uploads/[...path]/route.tsitself, as Media & Storage shows. - 14
To roll back, use your host's rollback of the alpha.71 build, not a fresh alpha.71 build.
Building alpha.71 against a database alpha.72 migrated stops with
NEXTLY_CORE_DESTRUCTIVE_REFUSED, because alpha.71 would drop the newemail_queuecolumns; it writes nothing. An alpha.71 build made before the upgrade runs on the alpha.72 schema.
What's changed37
Released 21 packages at 0.0.2-alpha.72 in lockstep. Every package below ships at this version.
Patch Changes
- A keyed send now holds up through crashes, slow providers and partial failures.
More
A send that reached some recipients and not others reports each recipient's outcome (
partlyDelivered,recipients), and a retry with the same key sends only to those not yet accepted, whichever provider carries it. Every provider call has a 2-minute deadline and a cancellation signal; a call that has not answered by then is reported as unknown and retryable, and its claim stays held for up to 10 minutes while the call may still answer. A failure whose delivery cannot be known carriesunknown: true. A keyed send's delivery records and its outcome are committed together. Sends whose recipients all fare alike return the result they always did. - A publish that sends a related many-to-many field with an id repeated, such as
["a", "b", "a"]over["a", "b"], is no longer recorded as editing that field.More
The ids are compared as a set: each id counts once, whatever its order.
- A Single's publish that sends a whole group or field group no longer loses a pending edit to a field inside it that the publisher may not write.
More
The publisher's copy of that field is judged at the pending change's value rather than the live one, so the publish is refused, naming the field, and the pending change is kept. A publish whose pending change leaves that field alone goes through as before.
- An entry you may not read is answered exactly as one that doesn't exist: 404
NOT_FOUND, "Not found.", whatever you asked to do with it.More
An entry you may read but not change is still answered 403, with what you may not do.
This holds for a request naming one collection entry or a Single's document, through REST, the Direct API, plugin services and the routes built on them, the document lock among them. The Direct API and plugin services answer an update, delete or publish of an entry you may not read with 404, as REST does. Whether you may read is asked as a plain read is, in the site's default language.
One place does not take that answer at its word: a dashboard card reading a Single. A card for a Single you may not read fails instead of showing "Nothing yet". Singles are the site's own configuration, defined in code, so their names are not secret, and an empty card for a refusal would tell the admin something false. A Single that really has no document still shows as empty.
A client that reads the status of a refused request on one entry should read the code instead:
NOT_FOUNDfor one it may not read,FORBIDDENfor one it may read but not change. A caller with no identity keeps its current answer. - A Breakdown card's Collection setting now offers only collections the card can divide, even when the card is already set to one: each collection is judged from the widget's declaration, not from the card as already asked.
More
A widget whose cells all depend on a condition the collection doesn't meet is no longer offered on that collection.
Registration now refuses a grouped query that leaves its key to a Group by setting the query's source doesn't take.
GET /me/capabilitiesnow names only the collections and Singles the signed-in caller may read, the same ones the collection and Single lists show that caller.More
Before, it named every collection and Single in the install, with
read: falsewhere the caller could not read one, so any signed-in user could learn every entity's name. A client that treated a missing entity as "nothing allowed" sees no change in what it may offer.- Dashboard cards set to a collection are judged more precisely: - Which collections a reader may read is now asked of every collection a card may be set to, not read off core's generated cards.
More
Two collections whose names differ only by
_and-(for examplea_banda-b) lose their generated cards to the clash, but a Count, Recent entries, Over time or Breakdown card set to either is now shown to a reader who may read it, instead of being withheld. - A widget's Collection setting withholds a card only where the setting actually decides what the card reads. A widget whose registration replaced its query, so the setting no longer reaches it, no longer has its cards withheld for a collection it never reads. - A saved count card converted for a reader who may not see the Count widget takes the widths Count is registered with, not the core default. - The admin's cached workspace data is refreshed when a widget's Collection setting changes, or when the collections a reader may read change, so the settings sheet never shows an outdated setting. - Recent entries and Over time read what their settings name, and nothing else. - Recent entries, or any titled list, set to a collection it cannot title (one naming no title field or having no
updatedAt) reads nothing and shows that it is not available, rather than the collection its widget was declared with. - Over time over 12 months, read on February 29, starts on February 28 of the year before, not on March 1, so its first month is drawn.More
Drawn as bars, the first and last bars are as wide as the rest and inside the chart. - Widget queries: a timeseries with a
rangerefuses alimit, which the range's own number of points would otherwise silently replace. - Saved dashboard cards from core's generated per-collection widgets now become the widgets offered per collection, as saved count and health cards already became Count: - a list or table card becomes Recent entries, which shows each entry's title, status and when it was updated from medium size, and is a table when drawn large; - a timeline card becomes Over time, over the last 30 days, when it counted entries by when they were created; - a breakdown card becomes Breakdown, by status, while its collection still has a draft and published lifecycle.
More
Each converted card is set to its collection, exactly the card a reader adds from the add panel; a Breakdown card also keeps grouping by status. It keeps its place, and its width is drawn at the nearest one its new widget allows. A collection's list and table cards become one Recent entries card. A converted card joins a card of the same widget the dashboard already holds for that collection only when that card has no other settings, so a filtered list stays as it was. A card is converted only when it can be converted exactly. Otherwise it stays as stored, out of view, and is never rewritten: a card whose collection the reader may not read (until they may), a timeline that counted by another date, a breakdown whose collection has no lifecycle any more, a deleted collection's card, and a plugin's card under a generated-looking id while that plugin is disabled.
- A dashboard card set to a collection the reader may not read is no longer handed to them.
More
Their layout read leaves it out, exactly as it leaves out a card of a widget they may not see, and their saves keep it in place, so it returns once they may read the collection. A collection that does not exist is treated the same way, so a reader cannot learn which collections exist from what comes back. A save that adds such a card is refused, with one answer whether the collection exists or not. The card's collection is read as its settings resolve it, its declared default included, and only a value shaped like a collection name names one: a special value such as
*withholds nothing.Saved count and health cards converting to Count cards also change in four ways:
- A card whose id two collections the reader may read could spell is set to no collection. A collection the reader may not read changes nothing: the card is set to the one readable collection that spells its id, so the result never tells the reader that the other exists.
- The merged card's width comes from the Count widget as registered, including a plugin's changes to it.
- A layout saved from a snapshot taken before a collection was renamed between such spellings (
a_btoa-b) is refused, so it cannot write the old name back. - The collections the conversion reads come from the same refresh as the reader's cards.
- A reader can add a Breakdown card from a collection's group in the add panel.
More
It shows how that collection's entries divide: by status on a collection with a draft and published lifecycle, otherwise by its first field with choices. Breakdown is offered only on collections with something to divide by, in the add panel and in the card's Collection setting alike. A field guarded by a read rule is never offered as a grouping. A stored grouping that names one falls back to the collection's own grouping, and a query naming one is still refused.
For widget authors, a Group by setting can take its default from the collection with
defaultFrom: "collection". A grouped query that leaves its key to such a setting may omitgroupBy. Both are@experimental. - Removed from
nextly/auth, deprecated in 0.0.2-alpha.69: the role checkshasRole,hasAnyRoleandhasAllRoles, the guardsrequireRole,requireAnyRoleandrequireAllRoles,checkPermission,authenticateApiKey,checkCollectionAccess, and the sessionAuthContexttype.More
The role checks compared role ids with role names, so they never matched. To ask whether a signed-in user may do something, use
nextly.access.check({ userId, resource, operation }); for an API key, usenextly.access.checkApiKey({ rawKey }), which returns the key's own permissions and roles (it runs no collection rule, so if you passedcheckCollectionAccessanevaluateCodeAccess, keep running that collection rule too); for super-admin authority, useisSuperAdmin(userId). The error helperscreateErrorResponse,createJsonErrorResponseandisErrorResponsestay.
All 37 changes
- The Email hub's Design tab now edits the brand settings beside a live preview.
More
Pick an email, and the preview shows each change to the logo, its width, the accent, background and content colours, the font and the footer before anything is saved; a save bar says how many emails a save changes. Where the page builder's site style defines colours, Match my site offers its light-theme colours for each part of the email, suggesting the ones named for each, and the site's font where email clients have it or the closest email-safe one, saying which. The tab says which emails the brand settings reach and why they don't reach the others, and warns when the site's own Default layout keeps them from its emails. Layouts list the emails each one wraps and open in the same workbench as an email, and a layout kept as the previous engine sent it, or with an update from its owner, is said there too. The draft preview (
POST /api/email-templates/preview) takes an optionalbrandchange, checked by the rules a save is and laid over the saved settings for that render only. - A rule's
permissionsno longer includes the earlierposts:readspelling, deprecated in 0.0.2-alpha.69: a rule that checksposts:readnow refuses, and nothing warns about it.More
Check the spelling the Roles screen shows,
read-posts. The list a rule is handed holds each permission once, as stored. - The entry list and editor offer only the actions your role allows.
More
A collection's list shows "New" (and the empty list's "Create" and the new-entry shortcut) only to someone who may create there. It shows row checkboxes and the bulk bar only when a bulk action (delete, publish or unpublish) is allowed, and "Delete selected", a row's Delete and the editor's Delete only to someone who may delete. Select-all and the Delete key follow the same answers. The quick-edit and quick-create windows opened from a relationship field offer Save or Create only with the update or create permission; without it the entry is shown read-only. The answers come from the server, code access rules included, and the server still enforces every request.
- Erasing someone now reaches their queued email (
@experimentalwith the queued send).More
Deleting a staff user, or a plugin reporting one of its people deleted, cancels every queued email that names them and clears its message, in the deletion's own transaction; erasing an address does the same for queued email to it that names nobody. An email queued for someone already deleted is stored cancelled and never sent, whichever of the two commits first. An attempt already with the provider is left to finish, and its delivery record is written erased.
Held messages are sealed again under the current
NEXTLY_SECRETwhen the app starts after a rotation, whileNEXTLY_SECRET_PREVIOUScan still open them, so a queued email stays deliverable across any number of rotations; the server log says how many no configured secret can open.A queued email now records the person it names, and a new
email_queue_recipientstable keeps a keyed digest of each recipient, never the address, so an erased address can be found.nextly migrateand boot add both on all three databases. - Field rules in field groups now hold at more of a write's edges. - A new instance's inline field groups: an update that adds a field-group instance creates the field groups it holds inline with it, so a child its create rule allows is stored rather than dropped.
More
A single field group's instance sent without its id is still judged as the update its save makes. - Repeated row ids: a repeater or field-group list row repeating another's id takes none of the stored row's protected values, and every row is judged by its own siblings. - Translated groups: in a localized collection, a field group held in a translated group keeps a child its update rule denies, in each language. - Fields named like store columns: a field a field group declares, named like one of the store's own columns (such as
updatedAtinside a group), is judged as content when a publish is checked, so a pending edit to it that the publisher may not write is refused rather than lost. - Held drafts in every language: a held draft that removes an instance keeps it when it holds a protected value in any language, without relying on the update's own check. - Protected passwords: an update that clears a field-group instance, or leaves it out of a list, keeps it when a rule protects its password, though the stored hash is never read back. - A field's write rule is now told the entry's id as stored, on an update, a transactional or batch update, a publish that puts a pending draft live, and taking a live value into a draft.
More
The
dataa field write rule reads no longer carries values only the server decides, as the request body sent them: theid,createdAt,updatedAt,createdByandfirstPublishedAt. On an update,idis the stored entry's. On a create, it is there only where the collection keeps the id a create sends (db.allowIdOnCreate), since that is the id the entry is stored under. - The Get started card and the deprecated onboarding read are corrected in five ways: - The deprecated
GET /api/dashboard/onboardinganswers as it always has until it is removed. A step whose own check failed or ran out of time is a row not complete again, and "not known" means only that the collections or Singles could not be listed.More
The Get started card still withholds such a step. - A failed sample-content load says so. It shows its message and a Retry, instead of the controls disappearing. - Loading sample content, or declining it, counts as acting on the card. A reader who finishes the last step from there is told "You're all set", and the card no longer simply goes. - A staged reset previews the default the reset will land on. It includes Get started even when the reader's own arrangement holds no such card. - The card draws every step the server sends, in the server's order. Runs of Singles still fold, and a step of a kind the admin does not know is drawn, not dropped.
- Each system list and its dashboard count read who may see that list from one definition.
More
The users, roles, permissions, media, field-group and API-key lists each declare their read gate once, in
auth/list-read-gates, and say how it is checked as well as what it asks. The users, roles, permissions and media gates are the system read policy's own, the one the checked Direct API reads, so the REST lists, the dashboard's counts and the Direct API admit the same callers. The list routes and the dashboard summary's counts both read those gates and check them the same way, so a count can no longer be given to a caller its list refuses, or kept from one it admits, after a change to only one of them. The API-key list's reach (every user's keys) is declared there too, and the key count covers what the list shows.No caller's answer changes. The permissions count is now checked through the any-of check the permissions list uses, rather than the single-grant check. For an API key and for a session the two agree on
read-roles. - Role assignments and role inheritance are stored under the ids the users and roles themselves carry.
More
On MySQL, which matches ids without regard to letter case, a request naming a user or role in another case is stored as that row's own id, and a role named twice in two cases is recognized as one when it would inherit itself.
- Passwords are stored hashed at every depth, and no read returns one: not a read by id or in a list, a read of every translation, a version snapshot or a webhook payload.
- The
user_permission_cachetable is retired.More
Nothing has read it since permission answers stopped being stored, so a new install no longer creates it. While an existing database still has it, every access change empties it before the role version is published, so an instance still running an earlier release that reads it, even one upgrading past the release in between, never serves a revoked grant from it. Where
PERMISSION_CACHE_ENABLED=falsesays no instance reads it, it is left as it is. An existing database keeps it, and the startup warning names it with its row count, untilnextly migrateruns withNEXTLY_DROP_RETIRED_AUTH_TABLES=1. That drops it even when it holds rows, since they were only copies of answers recomputed from roles, and logs the drop;NEXTLY_DROP_NONEMPTY_RETIREDstill governs only the retiredaccountsandsessionstables. Until the drop, on MySQL, where the table has no foreign key tousers, a deleted user's rows stay in it; they name only the user's id and permissions.The
userPermissionCacheschema export, deprecated in the previous release, is removed fromnextly,nextly/schemasand the dialect schemas innextly/database, together with theusers.permissionCacherelation. Nothing in Nextly reads the table, so code that queried it reads only answers no longer written; remove those queries. The nameuser_permission_cacheis reserved, so an app table cannot take it. - A code access rule no longer lets in a signed-in user or an API key that lacks the permission: the permission is asked first, and the rule only narrows it, for collections, Singles, related rows and writes alike, as well as
nextly.access.check.More
Where a rule was letting such users in, grant the permission on the role screen; for this release, each request newly refused this way is logged once, naming the permission to grant.
- Publishing several languages at once asks the publish rule once for each language it puts live.
More
Publishing every language of an entry or a Single, and writing
status: "published"withlocale: "*", used to ask the publish rule once, told every language and no single one. A rule answering by language, such aspublish: ({ locale }) => locale !== "fr", was then asked with nolocaleand let French go live. Each of them now asks the rule once for each language going live, withlocaleset to that language andlocalesto every language going live, inside the publish, before anything is written. Any refusal refuses the whole publish, so no language goes live; the server log names the languages refused.Unpublishing every language is still asked once, in every language.
- A publish that only sends back what the draft already holds is no longer credited as the publisher's edit in Recently edited.
More
The draft and the document the publish writes are now compared once, as a stored snapshot holds them. So none of these counts as a change:
- a date sent as a
Datebeside the ISO string the draft keeps; - part of a component sent back as the draft holds it;
- many-to-many ids in another order.
A Single's own field named
createdBycounts as content, as it does for any field a person writes.Two dashboard fixes:
- A Note's text is drawn as written, so a leading indent keeps its Markdown meaning.
- A layout write holds a placement id reused for another widget to the new widget's text bound.
- a date sent as a
- A ranged widget query validates again unchanged, and settings say what they may hand back. - Widget queries: a timeseries over a
rangecarries nolimitonce validated, so whatvalidateWidgetQueryreturns validates again unchanged.More
A widget declaring a
limitnumber setting beside a ranged query is refused at registration and as a contribution, since its range sets how many points are drawn. -applyWidgetSettingsreturnsAppliedQuery<T>: the query as declared with each setting applied, or, for a titled list set to a collection it cannot title, the same with nosourceand noselect. The type is exported fromnextly/config.All of it is
@experimentalwhile the content widgets are built. - Refusals that aren't about permission now say why, and stay 403
FORBIDDEN: - A person-only action asked by an API key: "Only a signed-in person can manage API keys." (or webhooks, preview links, a dashboard layout, notices, or marking a version as seen). - A change or delete of a role or permission built into Nextly: "This role is built in, so it can't be changed." (or "deleted", or "permission"). - A trigger request from a site the installation doesn't allow: "This request came from a site that isn't allowed to make it." - A Single deleted withoutforce: "A Single can't be deleted from here."More
Each used to read "You don't have permission to perform this action.", which sent people looking for a permission that wouldn't help. One delete that was allowed is now refused. A built-in permission deleted by action and resource, rather than by id, was deleted when it was unassigned. It is now refused as the delete by id always refused it. A hidden internal permission is refused in the generic words, so a delete doesn't confirm it exists.
Refusals for want of permission are unchanged: they never say which check refused.
- New projects now ship the route that serves local uploads,
src/app/uploads/[...path]/route.ts, andcreate-nextly-appwrites it into an existing app it adds Nextly to, unless the app already answers/uploads.More
A production server then serves an upload the moment it finishes, with nothing to add by hand. An app created before this release adds the file itself, as Media & Storage shows.
- Security: fast-uri moves to 3.1.9. - fast-uri before 3.1.9 had URI-parsing flaws: scheme spoofing through percent-encoding, host confusion during canonicalization, and two lower-severity ones (GHSA-w74x-v6f9-j3xg, GHSA-wjr9-j795-jxcr, GHSA-xhq7-xmqj-769h, GHSA-333x-w4r6-6crp).
More
@nextlyhq/telemetryreaches it throughconfandajv, which validate only telemetry's own consent settings against a fixed schema, so no untrusted URI passes through it. The floor still rises, so no install can resolve an affected version. - Security: the MCP server SDK moves to 2.3.1, nodemailer to 10.2.1 and undici to 7.30.1. -
@nextlyhq/plugin-mcpmoves from@modelcontextprotocol/server2.0.0 to 2.3.1.More
Before 2.3.0, one HTTP request within the default size limit could exhaust the server's memory during validation (GHSA-46jr-4wx8-v453); the MCP endpoint validates every request, so it was reachable. Two other advisories fixed in 2.3.0 (GHSA-jm63-cq47-j5jp, GHSA-rvq5-wwqv-78pq) didn't apply: the endpoint already builds a fresh server for each request, and it doesn't use the SDK's bearer authentication. - nextly's optional
nodemailerpeer moves from^9.0.1to^10.2.1. nodemailer before 10.1.4 had address-parser denial-of-service and parsing flaws (GHSA-prgh-xp8r-p3m5, GHSA-v53p-9fqp-m79j, GHSA-8c8m-p3m8-cjg8 and others), and the SMTP provider passes the addresses it sends to through that parser. Apps using nodemailer for SMTP need nodemailer 10: runnpm install nodemailer@^10.2.1. nodemailer 10 needs Node.js 20 or later, which Nextly already requires, and nothing in Nextly's use of it changes. - undici, which@vercel/bloband the jsdom behind HTML sanitizing pull in, moves to 7.30.1, past three denial-of-service and integrity advisories (GHSA-xjj6-f33q-929c, GHSA-4qv8-2p9j-2685, GHSA-f72v-6w45-q472). Neither path was exposed in practice: Vercel Blob's client talks only to Vercel's API, and sanitizing loads nothing over the network. - The site config can shape the dashboard:
admin.dashboard, checked at startup. -widgets,hide,overrides,defaultLayout,suggestionsandreplaceare accepted and checked, but not applied yet in this version.More
Startup logs one warning naming each one a site sets. The site's own widgets, declared with
defineWidget()as a plugin's are, are checked together with the plugins' widgets, and other keys may name them. - Startup refuses a site config that names a widget that does not exist (with the nearest id), or that uses a keyadmin.dashboard, anoverridesentry or adefaultLayoutcard does not take (with the key it most likely meant). An id in thecollection/orsingle/namespace, a card core derives from the content later, is not refused as unknown at startup; its entry's shape is still checked. It also refuses: - a width a widget does not allow, or widths that widen rather than narrow; - a setting a widget does not declare, or a value it does not accept; - a title or a description that breaks the declaration rules; - a default dashboard with more cards than a dashboard holds, or that lists a widget allowing one card more than once; - a default or a replacement naming a hidden widget, or a replacement no plugin offers for that built-in. - A plugin'ssuggestandalternativesmay name only its own widgets, and an alternative only in place of a built-in that exists. A plugin's dashboard contribution is read once at startup, every check reads that copy, and a key it does not take is refused with the key it most likely meant. - All startup faults are reported together with the widget declarations' own. -AdminDashboardConfig,WidgetRef,DefaultLayoutCardandWidgetOverrideare exported fromnextly.All of it is
@experimentalwhile the dashboard widget SDK is built. - A template test email no longer goes to the template's Cc and Bcc.
More
Send test now reaches only the signed-in person who pressed it: the template's stored copies, such as a team inbox, are left out of the test, which carries sample data. Real sends of the template still copy them.
- The admin now learns from the server what you may do with each collection and Single, so a collection's code access rule is reflected in what it offers, not only the permissions your roles hold: the collection and Single pages, the dashboard's quick-create shortcuts and the navigation all follow that answer.
More
It is asked in the content language on screen, kept between pages rather than asked again by every component, and asked again on focus, when your soonest role assignment ends, after a change to roles, permissions or role assignments, and after a schema change. Once that role assignment has ended, the earlier answer no longer counts, even if asking again fails. Until the server has answered, no collection or Single is offered. The system sections (users, roles, media, settings, email, webhooks, releases) are still offered from your permission list, and stay available when only the answer about collections fails.
useCanand<Can>(exported by the admin and re-exported by@nextlyhq/plugin-sdk) now answer a read, create, update, delete, publish or unpublish of a collection or Single as the server decides it, code access rules included.More
useCan("update-posts")isfalsefor a role that holdsupdate-postswhen a code rule on Posts refuses that update, andtruewhere the server allows an action no permission names. The entry editor's publish and unpublish, bulk publish and unpublish, publishing every language, adding to a release and restoring a version follow that answer. Until the server has answered about collections and Singles, such a slug answersfalse, and once it has, such a slug for a resource the answer does not name answersfalseexcept to a super-admin, so give a plugin permission that is not about a collection or Single an action other than those six (export-reports) to check it withuseCan. Every other slug (a system resource such asread-webhooks, or a plugin's own permission such asmanage-seo) is still answered from the permissions your roles hold. The server still enforces every request either way.- A plugin's dashboard widget can be drawn by its own component.
More
A widget declared with
defineWidgetanddisplay: { component }is now offered on the dashboard. The admin draws its frame and its loading, no-permission and error states, and hands the component the props its declaration types:WidgetProps(data, settings, size, refreshing, retry). If the component throws while drawing, that card alone shows its error, with Retry.@nextlyhq/plugin-sdk/adminnow exports the widget kit, the parts core's own cards are drawn from:CardSkeleton,CardEmpty,CardUnavailable,CardError,CardFigure,cardNumber,cardChange,GroupsChartandOverTimeChart. They read the card's size and the admin's theme, so a plugin card matches core's in light and dark.defineWidgetand theWidgetProps,WidgetRenderSizeandWidgetDatatypes are exported from@nextlyhq/plugin-sdk, and the types from./widgetstoo. All of it is@experimental.Import the kit only through
@nextlyhq/plugin-sdk/admin. If a second copy of the admin is loaded, development warns about it. - A dashboard widget whose Collection setting defaults to a collection the reader may not read no longer names that collection in the admin's workspace data.
More
The default is left out for that reader, as the dashboard already leaves out their cards that rely on it. The widget is still offered for the collections they may read; a card they add from it is set to one of those before it saves.
Packages
@nextlyhq/adapter-drizzle@nextlyhq/adapter-mysql@nextlyhq/adapter-postgres@nextlyhq/adapter-sqlite@nextlyhq/admin@nextlyhq/admin-css@nextlyhq/blocks-engine@nextlyhq/blocks-react@nextlyhq/builder@nextlyhq/eslint-plugin@nextlyhq/plugin-form-builder@nextlyhq/plugin-mcp@nextlyhq/plugin-page-builder@nextlyhq/plugin-sdk@nextlyhq/plugin-seo@nextlyhq/storage-s3@nextlyhq/storage-uploadthing@nextlyhq/storage-vercel-blob@nextlyhq/uicreate-nextly-appnextly
Earlier releases
Releases before 0.0.2-alpha.66 are listed in Nextly's changelog on GitHub.