Issue Summary
Since 6.64.0, a custom integration's Admin API key can no longer read or manage member custom field definitions. Every request under /ghost/api/admin/members/metafields/ (browse, read, add, reorder, edit, destroy) returns 403 NoPermissionError: API tokens do not have permission to access this endpoint. On 6.63.0 the same key could list and create definitions. Staff access tokens still work, and integration keys can still read and write member values through /members/:id/.
This reopens the gap #30497 closed in 6.63: an integration can read members' values but not the definitions that describe them.
The cause is #30512, which moved these routes onto a router mounted at /members/metafields and runs mw.authAdminApi inside it. For integration tokens, tokenPermissionCheck takes the resource from the first segment of req.url and checks it against its allowlist, where members is allowed. Inside a mounted router Express strips the mount path from req.url, so the check sees custom instead of members and refuses the request. Logged from a running 6.64.0:
GET /ghost/api/admin/members/: req.url is /members/, req.baseUrl is /ghost/api/admin
GET /ghost/api/admin/members/metafields/custom/: req.url is /custom/, req.baseUrl is /ghost/api/admin/members/metafields
test/e2e-api/admin/member-custom-fields.test.ts calls these routes only with staff sessions, so the change went unnoticed. It is still present on main at a2c06fe.
Suggested fix: run mw.authAdminApi on the main router before the mount, where req.url is still /members/metafields/..., and keep the sub-router and its guards as they are. A PR with this change and e2e tests follows.
Steps to Reproduce
- On 6.63.0 with
labs.membersCustomFields enabled, create a custom integration and use its Admin API key.
GET /ghost/api/admin/members/metafields/custom/ returns 200 with the definitions, and a POST to the same path creates one (201).
- Upgrade to 6.64.0 and repeat both requests. Each returns 403 with "API tokens do not have permission to access this endpoint".
- The same
GET with a staff access token still returns 200.
Expected: an integration's Admin API key can read and manage custom field definitions, as it could in 6.63.0 and as #30497 intended.
Ghost Version
6.64.0 (also main at a2c06fe)
Node.js Version
22.23.1
How did you install Ghost?
npm package in production mode on macOS, which uses the same routing as the official Docker image
Database type
Other: MySQL 9.6. The refusal comes from request routing, so the database doesn't matter.
Browser & OS version
Not applicable (Admin API)
Relevant log / error output
HTTP/1.1 403 Forbidden
{"errors":[{"message":"API tokens do not have permission to access this endpoint","type":"NoPermissionError"}]}
Code of Conduct
Issue Summary
Since 6.64.0, a custom integration's Admin API key can no longer read or manage member custom field definitions. Every request under
/ghost/api/admin/members/metafields/(browse, read, add, reorder, edit, destroy) returns403 NoPermissionError: API tokens do not have permission to access this endpoint. On 6.63.0 the same key could list and create definitions. Staff access tokens still work, and integration keys can still read and write member values through/members/:id/.This reopens the gap #30497 closed in 6.63: an integration can read members' values but not the definitions that describe them.
The cause is #30512, which moved these routes onto a router mounted at
/members/metafieldsand runsmw.authAdminApiinside it. For integration tokens,tokenPermissionChecktakes the resource from the first segment ofreq.urland checks it against its allowlist, wheremembersis allowed. Inside a mounted router Express strips the mount path fromreq.url, so the check seescustominstead ofmembersand refuses the request. Logged from a running 6.64.0:GET /ghost/api/admin/members/:req.urlis/members/,req.baseUrlis/ghost/api/adminGET /ghost/api/admin/members/metafields/custom/:req.urlis/custom/,req.baseUrlis/ghost/api/admin/members/metafieldstest/e2e-api/admin/member-custom-fields.test.tscalls these routes only with staff sessions, so the change went unnoticed. It is still present onmainat a2c06fe.Suggested fix: run
mw.authAdminApion the main router before the mount, wherereq.urlis still/members/metafields/..., and keep the sub-router and its guards as they are. A PR with this change and e2e tests follows.Steps to Reproduce
labs.membersCustomFieldsenabled, create a custom integration and use its Admin API key.GET /ghost/api/admin/members/metafields/custom/returns 200 with the definitions, and aPOSTto the same path creates one (201).GETwith a staff access token still returns 200.Expected: an integration's Admin API key can read and manage custom field definitions, as it could in 6.63.0 and as #30497 intended.
Ghost Version
6.64.0 (also
mainat a2c06fe)Node.js Version
22.23.1
How did you install Ghost?
npm package in production mode on macOS, which uses the same routing as the official Docker image
Database type
Other: MySQL 9.6. The refusal comes from request routing, so the database doesn't matter.
Browser & OS version
Not applicable (Admin API)
Relevant log / error output
HTTP/1.1 403 Forbidden {"errors":[{"message":"API tokens do not have permission to access this endpoint","type":"NoPermissionError"}]}Code of Conduct