{"id":16226225,"url":"https://github.com/ghostdevv/svelte-turnstile","last_synced_at":"2025-04-09T05:10:50.676Z","repository":{"id":60469033,"uuid":"543333969","full_name":"ghostdevv/svelte-turnstile","owner":"ghostdevv","description":"A lightweight Svelte component for Cloudflare Turnstile","archived":false,"fork":false,"pushed_at":"2024-09-24T03:53:23.000Z","size":192,"stargazers_count":175,"open_issues_count":4,"forks_count":11,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-10-11T12:48:23.996Z","etag":null,"topics":["hacktoberfest"],"latest_commit_sha":null,"homepage":"https://svelte-turnstile.pages.dev/","language":"Svelte","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ghostdevv.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"patreon":"onlyspaceghost","custom":"https://ghostdev.xyz/donate"}},"created_at":"2022-09-29T21:58:22.000Z","updated_at":"2024-10-10T07:53:21.000Z","dependencies_parsed_at":"2024-07-10T19:31:24.603Z","dependency_job_id":"3d45f753-0d33-4e16-8e16-249301b85c71","html_url":"https://github.com/ghostdevv/svelte-turnstile","commit_stats":{"total_commits":35,"total_committers":1,"mean_commits":35.0,"dds":0.0,"last_synced_commit":"91eef4bae19d542a9ddb93274d3db4030a736e9d"},"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ghostdevv%2Fsvelte-turnstile","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ghostdevv%2Fsvelte-turnstile/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ghostdevv%2Fsvelte-turnstile/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ghostdevv%2Fsvelte-turnstile/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ghostdevv","download_url":"https://codeload.github.com/ghostdevv/svelte-turnstile/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247980837,"owners_count":21027808,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hacktoberfest"],"created_at":"2024-10-10T12:48:31.207Z","updated_at":"2025-04-09T05:10:50.655Z","avatar_url":"https://github.com/ghostdevv.png","language":"Svelte","funding_links":["https://patreon.com/onlyspaceghost","https://ghostdev.xyz/donate"],"categories":[],"sub_categories":[],"readme":"# Svelte Turnstile\n\nWorks with Svelte 3, 4, and 5 (compatibility mode)!\n\n[Cloudflare's Turnstile](https://developers.cloudflare.com/turnstile/) is a new CAPTCHA alternative, this library allows you to easily integrate it into your svelte projects.\n\n# Installing\n\n```sh\nnpm install svelte-turnstile -D\n```\n\n# Demo\n\nhttps://svelte-turnstile.willow.codes\n\n# Using\n\nThe only required prop is the `siteKey` which you can get from [adding a site here](https://dash.cloudflare.com/?to=/:account/turnstile).\n\n```svelte\n\u003cscript\u003e\n\timport { Turnstile } from 'svelte-turnstile';\n\u003c/script\u003e\n\n\u003cTurnstile siteKey=\"SITE_KEY\" /\u003e\n```\n\n## Props\n\n| Prop                | Type                                                 | Description                                                                                    | Required |\n| ------------------- | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------- | -------- |\n| `siteKey`           | `string`                                             | sitekey for your website                                                                       | ✅       |\n| `theme`             | `'light' \\| 'dark' \\| 'auto'`                        | colour theme of the widget (defaults to `auto`)                                                |          |\n| `size`              | `'normal' \\| 'flexible' \\| 'invisible' \\| 'compact'` | size of the widget (defaults to `normal`)                                                      |          |\n| `action`            | `string`                                             | A string that can be used to differentiate widgets, returned on validation                     |          |\n| `cData`             | `string`                                             | A string that can attach customer data to a challange, returned on validation                  |          |\n| `tabIndex`          | `number`                                             | Used for accessibility (defaults to `0`)                                                       |          |\n| `responseField`     | `boolean`                                            | if true the response token will be a property on the form data (default `true`)                |          |\n| `responseFieldName` | `string`                                             | the `name` of the input which will appear on the form data (default `cf-turnstile-response`)   |          |\n| `retry`             | `'auto' \\| 'never'`                                  | should the widget automatically retry to obtain a token if it did not succeed (default `auto`) |          |\n| `retryInterval`     | `number`                                             | if `retry` is true, this controls the time between attempts in milliseconds (default `8000`)   |          |\n| `language`          | `SupportedLanguage \\| 'auto'`                        | the language turnstile should use (default `auto`)                                             |          |\n| `execution`         | `'render' \\| 'execute'`                              | controls when to obtain the token of the widget (default `render`)                             |          |\n| `appearance`        | `'always' \\| 'execute' \\| 'interaction-only'`        | controls when the widget is visible. (default `always`)                                        |          |\n\nFor more information about some of the props and a list of `SupportedLanguage`'s [checkout the Cloudflare Documentation](https://developers.cloudflare.com/turnstile/get-started/client-side-rendering/#configurations).\n\n### Deprecated Props\n\n- `forms` renamed to `responseField`\n- `formsField` renamed to `responseFieldName`\n\n## Events\n\n| Event                | Data                                                | Description                                                    |\n| -------------------- | --------------------------------------------------- | -------------------------------------------------------------- |\n| `callback`           | `{ token: string; preClearanceObtained: boolean; }` | Emitted when a user passes a challenge                         |\n| `error`              | `{ code: string }`                                  | Emitted when a user fails verification                         |\n| `expired`            | `{}`                                                | Emitted when a challenge expires and does not reset the widget |\n| `timeout`            | `{}`                                                | Emitted when a challenge expires and does reset the widget     |\n| `before-interactive` | `{}`                                                | Emitted before the challenge enters interactive mode           |\n| `after-interactive`  | `{}`                                                | Emitted when the challenge has left interactive mode           |\n| `unsupported`        | `{}`                                                | Emitted when a given client/browser is not supported           |\n\n# Validate CAPTCHA\n\nWe need to validate the captcha token server side before we do any action on the server, this is to ensure no forgery occured. We can create a simple validate function:\n\nIf you are using a HTML Form and POSTing to a server you can get the `cf-turnstile-response` (or what you configured it to using the `responseFieldName` option) property to get the `token`, otherwise you can use the `on:callback` event in svelte to keep track of the token and send it to your backend.\n\n```ts\ninterface TokenValidateResponse {\n\t'error-codes': string[];\n\tsuccess: boolean;\n\taction: string;\n\tcdata: string;\n}\n\nasync function validateToken(token: string, secret: string) {\n\tconst response = await fetch(\n\t\t'https://challenges.cloudflare.com/turnstile/v0/siteverify',\n\t\t{\n\t\t\tmethod: 'POST',\n\t\t\theaders: {\n\t\t\t\t'content-type': 'application/json',\n\t\t\t},\n\t\t\tbody: JSON.stringify({\n\t\t\t\tresponse: token,\n\t\t\t\tsecret: secret,\n\t\t\t}),\n\t\t},\n\t);\n\n\tconst data: TokenValidateResponse = await response.json();\n\n\treturn {\n\t\t// Return the status\n\t\tsuccess: data.success,\n\n\t\t// Return the first error if it exists\n\t\terror: data['error-codes']?.length ? data['error-codes'][0] : null,\n\t};\n}\n```\n\n## SvelteKit Example (Svelte 5)\n\nIn SvelteKit we can use form actions to easily setup a form with a captcha:\n\n`routes/login/+page.svelte`\n\n```svelte\n\u003cscript\u003e\n\timport { Turnstile } from 'svelte-turnstile';\n\n\tlet { form } = $props();\n\u003c/script\u003e\n\n{#if form?.error}\n\t\u003cp\u003e{form?.error}\u003c/p\u003e\n{/if}\n\n\u003cform method=\"POST\" action=\"/login\"\u003e\n\t\u003cTurnstile siteKey=\"SITE_KEY\" theme=\"dark\" /\u003e\n\u003c/form\u003e\n```\n\n`routes/login/+page.server.js`\n\n```js\n// Copy and paste the validateToken function from above here\n\nexport const actions = {\n\tdefault: async ({ request }) =\u003e {\n\t\tconst data = await request.formData();\n\n\t\tconst token = data.get('cf-turnstile-response'); // if you edited the formsField option change this\n\t\tconst SECRET_KEY = '...'; // you should use $env module for secrets\n\n\t\tconst { success, error } = await validateToken(token, SECRET_KEY);\n\n\t\tif (!success)\n\t\t\treturn {\n\t\t\t\terror: error || 'Invalid CAPTCHA',\n\t\t\t};\n\n\t\t// do something, the captcha is valid!\n\t},\n};\n```\n\n## Superforms Example (Svelte 5)\n\n`routes/login/schema.ts`\n\n```ts\nimport { z } from \"zod\";\n\nexport const schema = z.object({\n\t..., // other fields\n    'cf-turnstile-response': z.string().nonempty('Please complete turnstile')\n});\n```\n\n`routes/login/+page.svelte`\n\n```svelte\n\u003cscript lang=\"ts\"\u003e\n\timport { zodClient } from 'sveltekit-superforms/adapters';\n\timport { superForm } from 'sveltekit-superforms';\n\timport { Turnstile } from 'svelte-turnstile';\n\timport { schema } from './schema.ts';\n\n\tlet { data } = $props();\n\n\t// Call this to reset the turnstile\n\tlet reset = $state\u003c() =\u003e void\u003e();\n\n\tconst { enhance, message } = superForm(data.form, {\n\t\tvalidators: zodClient(schema),\n\t\tonUpdated() {\n\t\t\t// When the form is updated, we reset the turnstile\n\t\t\treset?.();\n\t\t},\n\t});\n\u003c/script\u003e\n\n\u003cform method=\"POST\" use:enhance\u003e\n\t\u003cTurnstile siteKey={PUBLIC_TURNSTILE_SITE_KEY} bind:reset /\u003e\n\u003c/form\u003e\n```\n\n`routes/login/+page.server.js`\n\n```js\nimport { fail, message, setError, superValidate } from 'sveltekit-superforms';\nimport { zod } from 'sveltekit-superforms/adapters';\nimport { schema } from './schema.ts';\n\nexport const load = async () =\u003e {\n\tconst form = await superValidate(zod(schema));\n\treturn { form };\n};\n\nexport const actions = {\n\tdefault: async ({ request }) =\u003e {\n\t\tconst form = await superValidate(request, zod(schema));\n\t\tif (!form.valid) return fail(400, { form });\n\n\t\tconst { success } = await validateToken(\n\t\t\tform.data['cf-turnstile-response'],\n\t\t\tSECRET_KEY,\n\t\t);\n\n\t\tif (!success) {\n\t\t\treturn setError(\n\t\t\t\tform,\n\t\t\t\t'cf-turnstile-response',\n\t\t\t\t'Invalid turnstile, please try again',\n\t\t\t);\n\t\t}\n\n\t\treturn message(form, 'Success!');\n\t},\n};\n```\n\nThis example uses the [Superforms onUpdated event](https://superforms.rocks/concepts/events) to reset the Turnstile widget. Additionally, it automatically adds the Turnstile response token to the form data.\n\n# Resetting\n\nIf you need to manually reset the widget, you can do so by binding to the `reset` prop. For example:\n\n```svelte\n\u003cscript lang=\"ts\"\u003e\n\tlet reset = $state\u003c() =\u003e void\u003e();\n\u003c/script\u003e\n\n\u003cbutton onclick={() =\u003e reset?.()}\u003e Reset \u003c/button\u003e\n\n\u003cTurnstile bind:reset /\u003e\n```\n\n# Support\n\n- Join the [discord](https://discord.gg/2Vd4wAjJnm)\u003cbr\u003e\n- Create a issue on the [github](https://github.com/ghostdevv/svelte-turnstile)\n\n# Notable Changes\n\nFull Changelog: https://github.com/ghostdevv/svelte-turnstile/releases\n\n- Deprecate `forms` prop in favour of `responseField`\n- Deprecate `formsField` prop in favour of `responseFieldName`\n- Deprecate the `on:turnstile-callback` event in favour of `on:callback`\n- Deprecate the `on:turnstile-error` event in favour of `on:error`\n- Deprecate the `on:turnstile-timeout` event in favour of `on:timeout`\n- Deprecate the `on:turnstile-expired` event in favour of `on:expired`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fghostdevv%2Fsvelte-turnstile","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fghostdevv%2Fsvelte-turnstile","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fghostdevv%2Fsvelte-turnstile/lists"}