diff --git a/frontend/src/routes/documents/bulk-edit/+page.server.ts b/frontend/src/routes/documents/bulk-edit/+page.server.ts
new file mode 100644
index 00000000..33c845a0
--- /dev/null
+++ b/frontend/src/routes/documents/bulk-edit/+page.server.ts
@@ -0,0 +1,10 @@
+import { redirect } from '@sveltejs/kit';
+
+export async function load({ locals }: { locals: App.Locals }) {
+ const canWrite =
+ locals.user?.groups?.some((g: { permissions: string[] }) =>
+ g.permissions.includes('WRITE_ALL')
+ ) ?? false;
+ if (!canWrite) throw redirect(303, '/documents');
+ return { canWrite };
+}
diff --git a/frontend/src/routes/documents/bulk-edit/+page.svelte b/frontend/src/routes/documents/bulk-edit/+page.svelte
new file mode 100644
index 00000000..b4018b8e
--- /dev/null
+++ b/frontend/src/routes/documents/bulk-edit/+page.svelte
@@ -0,0 +1,53 @@
+
+
+
+ {m.bulk_edit_title()} – Familienarchiv
+
+
+{#if loading}
+
…
+{:else if error}
+
+ {error}
+
+{:else if entries.length > 0}
+
+{/if}
diff --git a/frontend/src/routes/documents/bulk-edit/page.server.spec.ts b/frontend/src/routes/documents/bulk-edit/page.server.spec.ts
new file mode 100644
index 00000000..9f52a1a0
--- /dev/null
+++ b/frontend/src/routes/documents/bulk-edit/page.server.spec.ts
@@ -0,0 +1,46 @@
+import { describe, expect, it } from 'vitest';
+import { load } from './+page.server';
+
+describe('/documents/bulk-edit +page.server.ts', () => {
+ it('redirects to /documents when user lacks WRITE_ALL', async () => {
+ const locals = { user: { groups: [{ permissions: ['READ_ALL'] }] } };
+ try {
+ // @ts-expect-error — partial event shape sufficient for this guard
+ await load({ locals });
+ throw new Error('expected redirect to be thrown');
+ } catch (e) {
+ const err = e as { status?: number; location?: string };
+ expect(err.status).toBe(303);
+ expect(err.location).toBe('/documents');
+ }
+ });
+
+ it('redirects when user has no groups', async () => {
+ const locals = { user: { groups: [] } };
+ try {
+ // @ts-expect-error — partial event shape sufficient for this guard
+ await load({ locals });
+ throw new Error('expected redirect');
+ } catch (e) {
+ expect((e as { status?: number }).status).toBe(303);
+ }
+ });
+
+ it('redirects when no user is logged in', async () => {
+ const locals = {};
+ try {
+ // @ts-expect-error — partial event shape sufficient for this guard
+ await load({ locals });
+ throw new Error('expected redirect');
+ } catch (e) {
+ expect((e as { status?: number }).status).toBe(303);
+ }
+ });
+
+ it('returns canWrite=true for a WRITE_ALL user', async () => {
+ const locals = { user: { groups: [{ permissions: ['WRITE_ALL', 'READ_ALL'] }] } };
+ // @ts-expect-error — partial event shape sufficient for this guard
+ const result = await load({ locals });
+ expect(result).toEqual({ canWrite: true });
+ });
+});