diff --git a/.changeset/wise-boxes-develop.md b/.changeset/wise-boxes-develop.md
new file mode 100644
index 0000000000..5b7d0825e4
--- /dev/null
+++ b/.changeset/wise-boxes-develop.md
@@ -0,0 +1,44 @@
+---
+'astro': minor
+---
+
+Adds a new `getActionPath()` helper available from `astro:actions`
+
+Astro 5.1 introduces a new helper function, `getActionPath()` to give you more flexibility when calling your action.
+
+Calling `getActionPath()` with your action returns its URL path so you can make a `fetch()` request with custom headers, or use your action with an API such as `navigator.sendBeacon()`. Then, you can [handle the custom-formatted returned data](https://docs.astro.build/en/guides/actions/#handling-returned-data) as needed, just as if you had called an action directly.
+
+This example shows how to call a defined `like` action passing the `Authorization` header and the [`keepalive`](https://developer.mozilla.org/en-US/docs/Web/API/Request/keepalive) option:
+
+```astro
+
+```
+
+This example shows how to call the same `like` action using the [`sendBeacon`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon) API:
+
+```astro
+
+```
diff --git a/packages/astro/src/actions/plugins.ts b/packages/astro/src/actions/plugins.ts
index f5bd074dfc..4c1b930c3d 100644
--- a/packages/astro/src/actions/plugins.ts
+++ b/packages/astro/src/actions/plugins.ts
@@ -85,13 +85,13 @@ export function vitePluginActions({
code += `\nexport * from 'astro/actions/runtime/virtual/server.js';`;
} else {
code += `\nexport * from 'astro/actions/runtime/virtual/client.js';`;
- code = code.replace(
- "'/** @TRAILING_SLASH@ **/'",
- JSON.stringify(
- shouldAppendForwardSlash(settings.config.trailingSlash, settings.config.build.format),
- ),
- );
}
+ code = code.replace(
+ "'/** @TRAILING_SLASH@ **/'",
+ JSON.stringify(
+ shouldAppendForwardSlash(settings.config.trailingSlash, settings.config.build.format),
+ ),
+ );
return code;
},
};
diff --git a/packages/astro/templates/actions.mjs b/packages/astro/templates/actions.mjs
index 93aaa4d762..d10b2e3b34 100644
--- a/packages/astro/templates/actions.mjs
+++ b/packages/astro/templates/actions.mjs
@@ -1,5 +1,6 @@
import {
ActionError,
+ ACTION_QUERY_PARAMS,
appendForwardSlash,
deserializeActionResult,
getActionQueryString,
@@ -52,6 +53,17 @@ function toActionProxy(actionCallback = {}, aggregatedPath = '') {
});
}
+const SHOULD_APPEND_TRAILING_SLASH = '/** @TRAILING_SLASH@ **/';
+
+/** @param {import('astro:actions').ActionClient} */
+export function getActionPath(action) {
+ let path = `${import.meta.env.BASE_URL.replace(/\/$/, '')}/_actions/${new URLSearchParams(action.toString()).get(ACTION_QUERY_PARAMS.actionName)}`;
+ if (SHOULD_APPEND_TRAILING_SLASH) {
+ path = appendForwardSlash(path);
+ }
+ return path;
+}
+
/**
* @param {*} param argument passed to the action when called server or client-side.
* @param {string} path Built path to call action by path name.
@@ -88,19 +100,19 @@ async function handleAction(param, path, context) {
headers.set('Content-Length', '0');
}
}
+ const rawResult = await fetch(
+ getActionPath({
+ toString() {
+ return getActionQueryString(path);
+ },
+ }),
+ {
+ method: 'POST',
+ body,
+ headers,
+ },
+ );
- const shouldAppendTrailingSlash = '/** @TRAILING_SLASH@ **/';
- let actionPath = import.meta.env.BASE_URL.replace(/\/$/, '') + '/_actions/' + path;
-
- if (shouldAppendTrailingSlash) {
- actionPath = appendForwardSlash(actionPath);
- }
-
- const rawResult = await fetch(actionPath, {
- method: 'POST',
- body,
- headers,
- });
if (rawResult.status === 204) {
return deserializeActionResult({ type: 'empty', status: 204 });
}
diff --git a/packages/astro/test/actions.test.js b/packages/astro/test/actions.test.js
index 2af8ebdd97..929a2d8d84 100644
--- a/packages/astro/test/actions.test.js
+++ b/packages/astro/test/actions.test.js
@@ -588,6 +588,23 @@ it('Should support trailing slash', async () => {
await devServer.stop();
});
+it('getActionPath() should return the right path', async () => {
+ const fixture = await loadFixture({
+ root: './fixtures/actions/',
+ adapter: testAdapter(),
+ base: '/base',
+ trailingSlash: 'always',
+ });
+ const devServer = await fixture.startDevServer();
+ const res = await fixture.fetch('/base/get-action-path/');
+
+ assert.equal(res.ok, true);
+ const html = await res.text();
+ let $ = cheerio.load(html);
+ assert.equal($('[data-path]').text(), '/base/_actions/transformFormInput/');
+ await devServer.stop();
+});
+
/**
* Follow an expected redirect response.
*
diff --git a/packages/astro/test/fixtures/actions/src/pages/get-action-path.astro b/packages/astro/test/fixtures/actions/src/pages/get-action-path.astro
new file mode 100644
index 0000000000..9fa69b0e28
--- /dev/null
+++ b/packages/astro/test/fixtures/actions/src/pages/get-action-path.astro
@@ -0,0 +1,6 @@
+---
+import { actions, getActionPath } from "astro:actions"
+
+const path = getActionPath(actions.transformFormInput)
+---
+{path}
\ No newline at end of file
diff --git a/packages/astro/types/actions.d.ts b/packages/astro/types/actions.d.ts
index 90187ebb99..d30bd8bd99 100644
--- a/packages/astro/types/actions.d.ts
+++ b/packages/astro/types/actions.d.ts
@@ -1,3 +1,7 @@
declare module 'astro:actions' {
export * from 'astro/actions/runtime/virtual/server.js';
+
+ export function getActionPath(
+ action: import('astro/actions/runtime/virtual/server.js').ActionClient,
+ ): string;
}