diff --git a/.changeset/thirty-ghosts-cover.md b/.changeset/thirty-ghosts-cover.md
new file mode 100644
index 000000000000..a740728fdbcd
--- /dev/null
+++ b/.changeset/thirty-ghosts-cover.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/adapter-cloudflare-workers': minor
+---
+
+feat: allow additional handlers to be included in generated Cloudflare Worker
diff --git a/documentation/docs/25-build-and-deploy/70-adapter-cloudflare-workers.md b/documentation/docs/25-build-and-deploy/70-adapter-cloudflare-workers.md
index 71197d9daabe..66d769856856 100644
--- a/documentation/docs/25-build-and-deploy/70-adapter-cloudflare-workers.md
+++ b/documentation/docs/25-build-and-deploy/70-adapter-cloudflare-workers.md
@@ -30,6 +30,29 @@ export default {
 
 Path to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). If you would like to use a Wrangler configuration filename other than `wrangler.jsonc`, you can specify it using this option.
 
+### handlers
+
+Path to a file with additional [handlers](https://developers.cloudflare.com/workers/runtime-apis/handlers/) export alongside the SvelteKit-generated `fetch()` handler. Enables integration of, for example, `scheduled()` or `queue()` handlers with your SvelteKit app.
+
+Default: `undefined`- no additional handlers are exported. 
+
+The handlers file should export a default object with any additional handlers. Example below:
+
+```js
+// @errors: 2307 2377 7006
+/// file: src/handlers.js
+export default {
+  async scheduled(event, env, ctx) {
+    console.log("Scheduled trigger!");
+  },
+  // additional handlers go here
+}
+```
+
+> [!NOTE] The adapter expects the `handlers` file to have a default export.
+
+> [!NOTE] The adapter will overwrite any [fetch handler](https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/) exported from the `handlers` file in the generated worker. Most uses for a fetch handler are covered by endpoints or server hooks, so you should use those instead.
+
 ### platformProxy
 
 Preferences for the emulated `platform.env` local bindings. See the [getPlatformProxy](https://developers.cloudflare.com/workers/wrangler/api/#parameters-1) Wrangler API documentation for a full list of options.
diff --git a/packages/adapter-cloudflare-workers/files/entry.js b/packages/adapter-cloudflare-workers/files/entry.js
index 5f022e5096b9..fad7dffbe98d 100644
--- a/packages/adapter-cloudflare-workers/files/entry.js
+++ b/packages/adapter-cloudflare-workers/files/entry.js
@@ -1,7 +1,10 @@
 import { Server } from 'SERVER';
 import { manifest, prerendered, base_path } from 'MANIFEST';
+import handlers from 'HANDLERS';
 import { getAssetFromKV, mapRequestToAsset } from '@cloudflare/kv-asset-handler';
 import static_asset_manifest_json from '__STATIC_CONTENT_MANIFEST';
+import { WorkerEntrypoint } from 'cloudflare:workers';
+
 const static_asset_manifest = JSON.parse(static_asset_manifest_json);
 
 const server = new Server(manifest);
@@ -11,100 +14,112 @@ const app_path = `/${manifest.appPath}`;
 const immutable = `${app_path}/immutable/`;
 const version_file = `${app_path}/version.json`;
 
-export default {
-	/**
-	 * @param {Request} req
-	 * @param {any} env
-	 * @param {any} context
-	 */
-	async fetch(req, env, context) {
-		await server.init({ env });
-
-		const url = new URL(req.url);
-
-		// static assets
-		if (url.pathname.startsWith(app_path)) {
-			/** @type {Response} */
-			const res = await get_asset_from_kv(req, env, context);
-			if (is_error(res.status)) return res;
-
-			const cache_control = url.pathname.startsWith(immutable)
-				? 'public, immutable, max-age=31536000'
-				: 'no-cache';
-
-			return new Response(res.body, {
-				headers: {
-					// include original headers, minus cache-control which
-					// is overridden, and etag which is no longer useful
-					'cache-control': cache_control,
-					'content-type': res.headers.get('content-type'),
-					'x-robots-tag': 'noindex'
-				}
-			});
-		}
+/**
+ * @param {Request} req
+ * @param {any} env
+ * @param {any} context
+ */
+async function fetch(req, env, context) {
+	await server.init({ env });
+
+	const url = new URL(req.url);
+
+	// static assets
+	if (url.pathname.startsWith(app_path)) {
+		/** @type {Response} */
+		const res = await get_asset_from_kv(req, env, context);
+		if (is_error(res.status)) return res;
+
+		const cache_control = url.pathname.startsWith(immutable)
+			? 'public, immutable, max-age=31536000'
+			: 'no-cache';
+
+		return new Response(res.body, {
+			headers: {
+				// include original headers, minus cache-control which
+				// is overridden, and etag which is no longer useful
+				'cache-control': cache_control,
+				'content-type': res.headers.get('content-type'),
+				'x-robots-tag': 'noindex'
+			}
+		});
+	}
 
-		let { pathname, search } = url;
-		try {
-			pathname = decodeURIComponent(pathname);
-		} catch {
-			// ignore invalid URI
-		}
+	let { pathname, search } = url;
+	try {
+		pathname = decodeURIComponent(pathname);
+	} catch {
+		// ignore invalid URI
+	}
 
-		const stripped_pathname = pathname.replace(/\/$/, '');
-
-		// prerendered pages and /static files
-		let is_static_asset = false;
-		const filename = stripped_pathname.slice(base_path.length + 1);
-		if (filename) {
-			is_static_asset =
-				manifest.assets.has(filename) ||
-				manifest.assets.has(filename + '/index.html') ||
-				filename in manifest._.server_assets ||
-				filename + '/index.html' in manifest._.server_assets;
-		}
+	const stripped_pathname = pathname.replace(/\/$/, '');
+
+	// prerendered pages and /static files
+	let is_static_asset = false;
+	const filename = stripped_pathname.slice(base_path.length + 1);
+	if (filename) {
+		is_static_asset =
+			manifest.assets.has(filename) ||
+			manifest.assets.has(filename + '/index.html') ||
+			filename in manifest._.server_assets ||
+			filename + '/index.html' in manifest._.server_assets;
+	}
 
-		let location = pathname.at(-1) === '/' ? stripped_pathname : pathname + '/';
-
-		if (
-			is_static_asset ||
-			prerendered.has(pathname) ||
-			pathname === version_file ||
-			pathname.startsWith(immutable)
-		) {
-			return get_asset_from_kv(req, env, context, (request, options) => {
-				if (prerendered.has(pathname)) {
-					url.pathname = '/' + prerendered.get(pathname).file;
-					return new Request(url.toString(), request);
-				}
-
-				return mapRequestToAsset(request, options);
-			});
-		} else if (location && prerendered.has(location)) {
-			if (search) location += search;
-			return new Response('', {
-				status: 308,
-				headers: {
-					location
-				}
-			});
-		}
+	let location = pathname.at(-1) === '/' ? stripped_pathname : pathname + '/';
+
+	if (
+		is_static_asset ||
+		prerendered.has(pathname) ||
+		pathname === version_file ||
+		pathname.startsWith(immutable)
+	) {
+		return get_asset_from_kv(req, env, context, (request, options) => {
+			if (prerendered.has(pathname)) {
+				url.pathname = '/' + prerendered.get(pathname).file;
+				return new Request(url.toString(), request);
+			}
 
-		// dynamically-generated pages
-		return await server.respond(req, {
-			platform: {
-				env,
-				context,
-				// @ts-expect-error lib.dom is interfering with workers-types
-				caches,
-				// @ts-expect-error req is actually a Cloudflare request not a standard request
-				cf: req.cf
-			},
-			getClientAddress() {
-				return req.headers.get('cf-connecting-ip');
+			return mapRequestToAsset(request, options);
+		});
+	} else if (location && prerendered.has(location)) {
+		if (search) location += search;
+		return new Response('', {
+			status: 308,
+			headers: {
+				location
 			}
 		});
 	}
-};
+
+	// dynamically-generated pages
+	return await server.respond(req, {
+		platform: {
+			env,
+			context,
+			// @ts-expect-error lib.dom is interfering with workers-types
+			caches,
+			// @ts-expect-error req is actually a Cloudflare request not a standard request
+			cf: req.cf
+		},
+		getClientAddress() {
+			return req.headers.get('cf-connecting-ip');
+		}
+	});
+}
+
+export default 'prototype' in handlers && handlers.prototype instanceof WorkerEntrypoint
+	? Object.defineProperty(handlers.prototype, 'fetch', {
+			value: fetch,
+			writable: true,
+			enumerable: false,
+			configurable: true
+		})
+	: Object.defineProperty(handlers, 'fetch', {
+			value: fetch,
+			writable: true,
+			enumerable: true,
+			configurable: true
+		});
 
 /**
  * @param {Request} req
diff --git a/packages/adapter-cloudflare-workers/index.d.ts b/packages/adapter-cloudflare-workers/index.d.ts
index 878b48649d5e..ab071e3e8649 100644
--- a/packages/adapter-cloudflare-workers/index.d.ts
+++ b/packages/adapter-cloudflare-workers/index.d.ts
@@ -9,6 +9,10 @@ export interface AdapterOptions {
 	 * Path to your {@link https://developers.cloudflare.com/workers/wrangler/configuration/ | Wrangler configuration file}.
 	 */
 	config?: string;
+	/**
+	 * Path to a file with additional {@link https://developers.cloudflare.com/workers/runtime-apis/handlers/ | handlers} and (optionally) {@link https://developers.cloudflare.com/durable-objects/ | Durable Objects} to be exported from the file the adapter generates.
+	 */
+	handlers?: string;
 	/**
 	 * Config object passed to {@link https://developers.cloudflare.com/workers/wrangler/api/#getplatformproxy | getPlatformProxy}
 	 * during development and preview.
diff --git a/packages/adapter-cloudflare-workers/index.js b/packages/adapter-cloudflare-workers/index.js
index 5d13539cd915..a380491e1675 100644
--- a/packages/adapter-cloudflare-workers/index.js
+++ b/packages/adapter-cloudflare-workers/index.js
@@ -1,6 +1,7 @@
 import { execSync } from 'node:child_process';
 import { writeFileSync } from 'node:fs';
-import { posix, dirname } from 'node:path';
+import { posix, dirname, resolve } from 'node:path';
+import { cwd } from 'node:process';
 import { fileURLToPath } from 'node:url';
 import esbuild from 'esbuild';
 import { getPlatformProxy, unstable_readConfig } from 'wrangler';
@@ -21,7 +22,7 @@ const compatible_node_modules = [
 ];
 
 /** @type {import('./index.js').default} */
-export default function ({ config, platformProxy = {} } = {}) {
+export default function ({ config, platformProxy = {}, handlers } = {}) {
 	return {
 		name: '@sveltejs/adapter-cloudflare-workers',
 
@@ -47,7 +48,8 @@ export default function ({ config, platformProxy = {} } = {}) {
 			builder.copy(`${files}/entry.js`, `${tmp}/entry.js`, {
 				replace: {
 					SERVER: `${relativePath}/index.js`,
-					MANIFEST: './manifest.js'
+					MANIFEST: './manifest.js',
+					HANDLERS: './_handlers.js'
 				}
 			});
 
@@ -67,6 +69,18 @@ export default function ({ config, platformProxy = {} } = {}) {
 					`export const base_path = ${JSON.stringify(builder.config.kit.paths.base)};\n`
 			);
 
+			if (handlers) {
+				// TODO: find a more robust way to resolve files relative to svelte.config.js
+				const handlers_file = resolve(cwd(), handlers);
+				writeFileSync(
+					`${tmp}/_handlers.js`,
+					`import handlers from "${handlers_file}";\n\n` + 'export default handlers;'
+				);
+			} else {
+				// The handlers file must export a plain object as its default export.
+				writeFileSync(`${tmp}/_handlers.js`, 'export default {};');
+			}
+
 			const external = ['__STATIC_CONTENT_MANIFEST', 'cloudflare:*'];
 			if (compatibility_flags && compatibility_flags.includes('nodejs_compat')) {
 				external.push(...compatible_node_modules.map((id) => `node:${id}`));
diff --git a/packages/adapter-cloudflare-workers/internal.d.ts b/packages/adapter-cloudflare-workers/internal.d.ts
index 3877ad52f4a5..d78b8bb9ce4c 100644
--- a/packages/adapter-cloudflare-workers/internal.d.ts
+++ b/packages/adapter-cloudflare-workers/internal.d.ts
@@ -14,3 +14,12 @@ declare module '__STATIC_CONTENT_MANIFEST' {
 	const json: string;
 	export default json;
 }
+
+declare module 'HANDLERS' {
+	import { ExportedHandler } from '@cloudflare/workers-types';
+	import { WorkerEntrypoint } from 'cloudflare:workers';
+
+	const handlers: Omit<ExportedHandler, 'fetch'> | WorkerEntrypoint;
+
+	export default handlers;
+}