diff --git a/src/content/docs/pages/functions/plugins/hcaptcha.mdx b/src/content/docs/pages/functions/plugins/hcaptcha.mdx index 2f03fe8bf867b36..9ba657bde988999 100644 --- a/src/content/docs/pages/functions/plugins/hcaptcha.mdx +++ b/src/content/docs/pages/functions/plugins/hcaptcha.mdx @@ -5,7 +5,7 @@ sidebar: order: 1 --- -import { Render } from "~/components" +import { Render } from "~/components"; The hCaptcha Pages Plugin validates hCaptcha tokens. @@ -37,8 +37,6 @@ export const onRequestPost: PagesFunction[] = [ ]; ``` - - This Plugin only exposes a single route. It will be available wherever it is mounted. In the above example, because it is mounted in `functions/register.ts`, it will validate requests to `/register`. The Plugin is mounted with a single object parameter with the following properties. [`secret`](https://dashboard.hcaptcha.com/settings) (mandatory) and [`sitekey`](https://dashboard.hcaptcha.com/sites) (optional) can both be found in your hCaptcha dashboard. diff --git a/src/content/docs/pages/functions/plugins/index.mdx b/src/content/docs/pages/functions/plugins/index.mdx index be52e8700dd7328..7944fff51e8b59e 100644 --- a/src/content/docs/pages/functions/plugins/index.mdx +++ b/src/content/docs/pages/functions/plugins/index.mdx @@ -3,16 +3,15 @@ pcx_content_type: concept title: Pages Plugins sidebar: order: 9 - --- -import { DirectoryListing, Render } from "~/components" +import { DirectoryListing, Render } from "~/components"; Cloudflare maintains a number of official Pages Plugins for you to use in your Pages projects: -*** +--- ## Author a Pages Plugin @@ -20,13 +19,13 @@ A Pages Plugin is a Pages Functions distributable which includes built-in routin For example, a Pages Plugin could: -* Intercept HTML pages and inject in a third-party script. -* Proxy a third-party service's API. -* Validate authorization headers. -* Provide a full admin web app experience. -* Store data in KV or Durable Objects. -* Server-side render (SSR) webpages with data from a CMS. -* Report errors and track performance. +- Intercept HTML pages and inject in a third-party script. +- Proxy a third-party service's API. +- Validate authorization headers. +- Provide a full admin web app experience. +- Store data in KV or Durable Objects. +- Server-side render (SSR) webpages with data from a CMS. +- Report errors and track performance. A Pages Plugin is essentially a library that developers can use to augment their existing Pages project with a deep integration to Functions. @@ -34,7 +33,7 @@ A Pages Plugin is essentially a library that developers can use to augment their Developers can enhance their projects by mounting a Pages Plugin at a route of their application. Plugins will provide instructions of where they should typically be mounted (for example, an admin interface might be mounted at `functions/admin/[[path]].ts`, and an error logger might be mounted at `functions/_middleware.ts`). Additionally, each Plugin may take some configuration (for example, with an API token). -*** +--- ## Static form example @@ -42,9 +41,9 @@ In this example, you will build a Pages Plugin and then include it in a project. The first Plugin should: -* intercept HTML forms. -* store the form submission in [KV](/kv/api/). -* respond to submissions with a developer's custom response. +- intercept HTML forms. +- store the form submission in [KV](/kv/api/). +- respond to submissions with a developer's custom response. ### 1. Create a new Pages Plugin @@ -52,46 +51,42 @@ Create a `package.json` with the following: ```json { - "name": "@cloudflare/static-form-interceptor", - "main": "dist/index.js", - "types": "index.d.ts", - "files": ["dist", "index.d.ts", "tsconfig.json"], - "scripts": { - "build": "npx wrangler pages functions build --plugin --outdir=dist", - "prepare": "npm run build" - } + "name": "@cloudflare/static-form-interceptor", + "main": "dist/index.js", + "types": "index.d.ts", + "files": ["dist", "index.d.ts", "tsconfig.json"], + "scripts": { + "build": "npx wrangler pages functions build --plugin --outdir=dist", + "prepare": "npm run build" + } } ``` :::note - The `npx wrangler pages functions build` command supports a number of arguments, including: -* `--plugin` which tells the command to build a Pages Plugin, (rather than Pages Functions as part of a Pages project) -* `--outdir` which allows you to specify where to output the built Plugin -* `--external` which can be used to avoid bundling external modules in the Plugin -* `--watch` argument tells the command to watch for changes to the source files and rebuild the Plugin automatically +- `--plugin` which tells the command to build a Pages Plugin, (rather than Pages Functions as part of a Pages project) +- `--outdir` which allows you to specify where to output the built Plugin +- `--external` which can be used to avoid bundling external modules in the Plugin +- `--watch` argument tells the command to watch for changes to the source files and rebuild the Plugin automatically For more information about the available arguments, run `npx wrangler pages functions build --help`. - ::: In our example, `dist/index.js` will be the entrypoint to your Plugin. This is a generated file built by Wrangler with the `npm run build` command. Add the `dist/` directory to your `.gitignore`. Next, create a `functions` directory and start coding your Plugin. The `functions` folder will be mounted at some route by the developer, so consider how you want to structure your files. Generally: -* if you want your Plugin to run on a single route of the developer's choice (for example, `/foo`), create a `functions/index.ts` file. -* if you want your Plugin to be mounted and serve all requests beyond a certain path (for example, `/admin/login` and `/admin/dashboard`), create a `functions/[[path]].ts` file. -* if you want your Plugin to intercept requests but fallback on either other Functions or the project's static assets, create a `functions/_middleware.ts` file. +- if you want your Plugin to run on a single route of the developer's choice (for example, `/foo`), create a `functions/index.ts` file. +- if you want your Plugin to be mounted and serve all requests beyond a certain path (for example, `/admin/login` and `/admin/dashboard`), create a `functions/[[path]].ts` file. +- if you want your Plugin to intercept requests but fallback on either other Functions or the project's static assets, create a `functions/_middleware.ts` file. :::note[Do not include the mounted path in your Plugin] - Your Plugin should not use the mounted path anywhere in the file structure (for example, `/foo` or `/admin`). Developers should be free to mount your Plugin wherever they choose, but you can make recommendations of how you expect this to be mounted in your `README.md`. - ::: You are free to use as many different files as you need. The structure of a Plugin is exactly the same as Functions in a Pages project today, except that the handlers receive a new property of their parameter object, `pluginArgs`. This property is the initialization parameter that a developer passes when mounting a Plugin. You can use this to receive API tokens, KV/Durable Object namespaces, or anything else that your Plugin needs to work. @@ -100,44 +95,48 @@ Returning to your static form example, if you want to intercept requests and ove ```typescript class FormHandler { - element(element) { - const name = element.getAttribute('data-static-form-name') - element.setAttribute('method', 'POST') - element.removeAttribute('action') - element.append(``, { html: true }) - } + element(element) { + const name = element.getAttribute("data-static-form-name"); + element.setAttribute("method", "POST"); + element.removeAttribute("action"); + element.append( + ``, + { html: true }, + ); + } } export const onRequestGet = async (context) => { - // We first get the original response from the project - const response = await context.next() + // We first get the original response from the project + const response = await context.next(); - // Then, using HTMLRewriter, we transform `form` elements with a `data-static-form-name` attribute, to tell them to POST to the current page - return new HTMLRewriter().on('form[data-static-form-name]', new FormHandler()).transform(response) -} + // Then, using HTMLRewriter, we transform `form` elements with a `data-static-form-name` attribute, to tell them to POST to the current page + return new HTMLRewriter() + .on("form[data-static-form-name]", new FormHandler()) + .transform(response); +}; export const onRequestPost = async (context) => { - // Parse the form - const formData = await context.request.formData() - const name = formData.get('static-form-name') - const entries = Object.fromEntries([...formData.entries()].filter(([name]) => name !== 'static-form-name')) - - // Get the arguments given to the Plugin by the developer - const { kv, respondWith } = context.pluginArgs - - - // Store form data in KV under key `form-name:YYYY-MM-DDTHH:MM:SSZ` - const key = `${name}:${new Date().toISOString()}` - context.waitUntil(kv.put(name, JSON.stringify(entries))) - - // Respond with whatever the developer wants - const response = await respondWith({ formData }) - return response -} + // Parse the form + const formData = await context.request.formData(); + const name = formData.get("static-form-name"); + const entries = Object.fromEntries( + [...formData.entries()].filter(([name]) => name !== "static-form-name"), + ); + + // Get the arguments given to the Plugin by the developer + const { kv, respondWith } = context.pluginArgs; + + // Store form data in KV under key `form-name:YYYY-MM-DDTHH:MM:SSZ` + const key = `${name}:${new Date().toISOString()}`; + context.waitUntil(kv.put(name, JSON.stringify(entries))); + + // Respond with whatever the developer wants + const response = await respondWith({ formData }); + return response; +}; ``` - - ### 2. Type your Pages Plugin To create a good developer experience, you should consider adding TypeScript typings to your Plugin. This allows developers to use their IDE features for autocompletion, and also ensure that they include all the parameters you are expecting. @@ -146,8 +145,8 @@ In the `index.d.ts`, export a function which takes your `pluginArgs` and returns ```typescript export type PluginArgs = { - kv: KVNamespace; - respondWith: (args: { formData: FormData }) => Promise; + kv: KVNamespace; + respondWith: (args: { formData: FormData }) => Promise; }; export default function (args: PluginArgs): PagesFunction; @@ -163,7 +162,7 @@ You can distribute your Plugin however you choose. Popular options include publi Make sure you are including the generated `dist/` directory, your typings `index.d.ts`, as well as a `README.md` with instructions on how developers can use your Plugin. -*** +--- ### 5. Install your Pages Plugin @@ -187,14 +186,14 @@ A Plugin's default export is a function which takes the same context parameter t import staticFormInterceptorPlugin from "@cloudflare/static-form-interceptor"; export const onRequest = (context) => { - return staticFormInterceptorPlugin({ - kv: context.env.FORM_KV, - respondWith: async ({ formData }) => { - // Could call email/notification service here - const name = formData.get("name"); - return new Response(`Thank you for your submission, ${name}!`); - }, - })(context); + return staticFormInterceptorPlugin({ + kv: context.env.FORM_KV, + respondWith: async ({ formData }) => { + // Could call email/notification service here + const name = formData.get("name"); + return new Response(`Thank you for your submission, ${name}!`); + }, + })(context); }; ``` @@ -207,20 +206,20 @@ With your Plugin mounted on the `/contact` route, a corresponding HTML file migh ```html - -

Contact us

- -
- - -
- + +

Contact us

+ +
+ + +
+ ``` @@ -234,7 +233,7 @@ If you experience any problems with any one Plugin, file an issue on that Plugin If you experience any problems with Plugins in general, we would appreciate your feedback in the #pages-discussions channel in [Discord](https://discord.com/invite/cloudflaredev)! We are excited to see what you build with Plugins and welcome any feedback about the authoring or developer experience. Let us know in the Discord channel if there is anything you need to make Plugins even more powerful. -*** +--- ## Chain your Plugin @@ -246,25 +245,26 @@ import cloudflareAccessPlugin from "@cloudflare/pages-plugin-cloudflare-access"; import adminDashboardPlugin from "@cloudflare/a-fictional-admin-plugin"; export const onRequest = [ - // Initialize a Sentry Plugin to capture any errors - sentryPlugin({ dsn: "https://sentry.io/welcome/xyz" }), + // Initialize a Sentry Plugin to capture any errors + sentryPlugin({ dsn: "https://sentry.io/welcome/xyz" }), - // Initialize a Cloudflare Access Plugin to ensure only administrators can access this protected route - cloudflareAccessPlugin({ - domain: "https://test.cloudflareaccess.com", - aud: "4714c1358e65fe4b408ad6d432a5f878f08194bdb4752441fd56faefa9b2b6f2", - }), + // Initialize a Cloudflare Access Plugin to ensure only administrators can access this protected route + cloudflareAccessPlugin({ + domain: "https://test.cloudflareaccess.com", + aud: "4714c1358e65fe4b408ad6d432a5f878f08194bdb4752441fd56faefa9b2b6f2", + }), - // Populate the Sentry plugin with additional information about the current user - (context) => { - const email = context.data.cloudflareAccessJWT.payload?.email || "service user"; + // Populate the Sentry plugin with additional information about the current user + (context) => { + const email = + context.data.cloudflareAccessJWT.payload?.email || "service user"; - context.data.sentry.setUser({ email }); + context.data.sentry.setUser({ email }); - return next(); - }, + return next(); + }, - // Finally, serve the admin dashboard plugin, knowing that errors will be captured and that every incoming request has been authenticated - adminDashboardPlugin(), + // Finally, serve the admin dashboard plugin, knowing that errors will be captured and that every incoming request has been authenticated + adminDashboardPlugin(), ]; ``` diff --git a/src/content/docs/pages/functions/plugins/turnstile.mdx b/src/content/docs/pages/functions/plugins/turnstile.mdx index 17a710653ad96bb..15eae3f4ae00c94 100644 --- a/src/content/docs/pages/functions/plugins/turnstile.mdx +++ b/src/content/docs/pages/functions/plugins/turnstile.mdx @@ -5,7 +5,7 @@ sidebar: order: 1 --- -import { Render } from "~/components" +import { Render } from "~/components"; [Turnstile](/turnstile/) is Cloudflare's smart CAPTCHA alternative. @@ -47,8 +47,6 @@ export const onRequestPost = [ ]; ``` - - This Plugin only exposes a single route to verify an incoming Turnstile response in a `POST` as the `cf-turnstile-response` parameter. It will be available wherever it is mounted. In the example above, it is mounted in `functions/register.ts`. As a result, it will validate requests to `/register`. ## Properties diff --git a/src/content/docs/pages/how-to/refactor-a-worker-to-pages-functions.mdx b/src/content/docs/pages/how-to/refactor-a-worker-to-pages-functions.mdx index 08b806943c957fe..9b7ad18ed16ceca 100644 --- a/src/content/docs/pages/how-to/refactor-a-worker-to-pages-functions.mdx +++ b/src/content/docs/pages/how-to/refactor-a-worker-to-pages-functions.mdx @@ -3,7 +3,7 @@ pcx_content_type: how-to title: Refactor a Worker to a Pages Function --- -import { Render } from "~/components" +import { Render } from "~/components"; In this guide, you will learn how to refactor a Worker made to intake form submissions to a Pages Function that can be hosted on your Cloudflare Pages application. [Pages Functions](/pages/functions/) is a serverless function that lives within the same project directory as your application and is deployed with Cloudflare Pages. It enables you to run server-side code that adds dynamic functionality without running a dedicated server. You may want to refactor a Worker to a Pages Function for one of these reasons: @@ -103,8 +103,6 @@ const HandleAirtableData = (body, env) => { }; ``` - - ### Refactor your Worker To refactor the above Worker, go to your Pages project directory and create a `/functions` folder. In `/functions`, create a `form.js` file. This file will handle form submissions. diff --git a/src/content/docs/pages/tutorials/forms/index.mdx b/src/content/docs/pages/tutorials/forms/index.mdx index a7a2f2ce29841b9..354e40e2fb85414 100644 --- a/src/content/docs/pages/tutorials/forms/index.mdx +++ b/src/content/docs/pages/tutorials/forms/index.mdx @@ -9,7 +9,7 @@ languages: - JavaScript --- -import { Render } from "~/components" +import { Render } from "~/components"; In this tutorial, you will create a simple `
` using plain HTML and CSS and deploy it to Cloudflare Pages. While doing so, you will learn about some of the HTML form attributes and how to collect submitted data within a Worker. @@ -273,8 +273,6 @@ With this handler in place, the example is now fully functional. When a submissi However, if you want to reply with a JSON object instead of the key-value pairs (an Array of Arrays), then you must do so manually. Recently, JavaScript added the [`Object.fromEntries`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/fromEntries) utility. This works well in some cases; however, the example `` includes a `movies` checklist that allows for multiple values. If using `Object.fromEntries`, the generated object would only keep one of the `movies` values, discarding the rest. To avoid this, you must write your own `FormData` to `Object` utility instead: - - ```js /** * POST /api/submit