Skip to content
JavaScript

Designing Robust Form Engines: Avoiding Swedish Will Witness Pitfalls

Swedish will-witness rules in TypeScript highlight common bugs in form engines. Learn how to prevent invalid document generation.

Topic
JavaScript
Reading time
6 min
Length
1,240 words
Published
Oct 7, 2026
07:28 pm IST
In this article
  1. Understanding Swedish Will-Witness Rules
  2. Bug 1: Accepting Disqualified Witnesses
  3. Bug 2: Misinterpreting the Personnummer
  4. Bug 3: Trusting Unseen User Inputs
  5. Implementing These Lessons
  6. Limitations and Considerations

Designing a form engine to generate Swedish wills is no small task, thanks to the tricky rules about who can and can't be a witness. A recent article has caught my eye, outlining four bugs developers absolutely must dodge. Let's see what these challenges are and how we might tackle them.

Understanding Swedish Will-Witness Rules

In Sweden, making a valid will is fairly straightforward but absolutely critical. Your will must be written down, signed by you with two witnesses present, and each of those witnesses must sign it too. There's no notary or registry involved, so the witness part is vital—get it wrong, and the whole thing, or parts of it, could become void.

Bug 1: Accepting Disqualified Witnesses

The first hurdle is making sure witnesses align with the eligibility criteria from the Swedish Inheritance Code. There are two groups of disqualified witnesses: those who can't witness any part of the will and those who can't witness specific provisions that benefit them or close family. For example, witnesses under 15 or those with a mental disorder aren't allowed to witness any part of the will. Meanwhile, if a witness could benefit from the will or is related to someone who would, they're out—at least for those parts.

To avoid this, your form engine has to thoroughly verify witness relationships. A data model that lists disqualifying relations might look like this:

type Option = { value: string; label: string };
export const WITNESS_RELATIONS: Option[] = [
  { value: 'none', label: 'No relation listed below' },
  { value: 'partner', label: "Testator's spouse or sambo" },
  { value: 'lineal', label: 'Child, grandchild, parent, grandparent or other straight-line relative' },
  { value: 'sibling', label: 'Sibling' },
  { value: 'inlaw', label: 'Brother-, sister-, parent-, son- or daughter-in-law' },
  { value: 'receiver', label: 'Receives something under the will, or is close family of someone who does' },
  { value: 'board', label: 'Board member of a company or organisation that receives something' },
  { value: 'under15', label: 'Under 15' },
  { value: 'guardian', label: 'Guardian, administrator, or attorney-in-fact for someone involved' },
  { value: 'capacity', label: 'Cannot understand what being a witness means' },
];

Your validator should also compare the witness names against others in the will to stop disqualified individuals from slipping through. This step is key—because users might not pick the right relationship option. Here's how you might code that validation:

function same(x: string, y: string): boolean {
  const norm = (s: string) => s.trim().toLowerCase().replace(/\s+/g, ' ');
  return !!x && !!y && norm(x) === norm(y);
}
// inside validate(answers)
const testators = [str(a, 't1_name')];
const receivers = [
  ...items(a, 'bequests').map((it) => str(it, 'name')),
  ...items(a, 'shares').map((it) => str(it, 'name')),
  str(a, 'partner_name'),
];
for (const i of [1, 2]) {
  const name = str(a, `w${i}_name`);
  if (!name) continue; // naming witnesses up front is optional
  const rel = str(a, `w${i}_relation`);
  if (rel && rel !== 'none') {
    issues.push({ level: 'error', field: `w${i}_relation`,
      message: `${name} cannot be a witness. ${WITNESS_ERRORS[rel]}` });
  } else if (testators.some((t) => same(t, name))) {
    issues.push({ level: 'error', field: `w${i}_name`,
      message: 'The person making the will cannot witness it.' });
  } else if (receivers.some((r) => same(r, name))) {
    issues.push({ level: 'error', field: `w${i}_name`,
      message: `${name} receives something under the will and cannot be a witness.` });
  }
}
if (same(str(a, 'w1_name'), str(a, 'w2_name'))) {
  issues.push({ level: 'error', field: 'w2_name', message: 'You need two different witnesses.' });
}

I've learned that edge cases matter a lot here, like ensuring the validator isn't too strict on small name discrepancies. To avoid false positives, name matching is kept simple—cases and extra spaces are ignored, but fuzzy matching that risks unrelated matches is avoided.

Bug 2: Misinterpreting the Personnummer

The Swedish personal identity number, personnummer, can cause all sorts of trouble. It might look straightforward, but it's packed with quirks:

  • 10 or 12 digits, but the Luhn checksum applies to just 10 digits.
  • The separator reflects age: '-' for under 100, '+' for 100 or older.
  • Coordination numbers add 60 to the day for non-residents.

You need a reliable normaliser to nail down these details and ensure the correct century and format:

export function normalisePnr(raw: string, today = new Date()): string | null {
  const s = raw.replace(/\s/g, '');
  const m = /^(\d{2})?(\d{2})(\d{2})(\d{2})([-+]?)(\d{4})$/.exec(s);
  if (!m) return null;
  let [, cent, yy, mm, dd, sep, last] = m;
  const day = Number(dd) > 60 ? Number(dd) - 60 : Number(dd);
  if (!cent) {
    let full = 2000 + Number(yy);
    if (Date.UTC(full, Number(mm) - 1, day) > today.getTime()) full -= 100;
    if (sep === '+') full -= 100;
    cent = String(full).slice(0, 2);
  }
  const ten = `${yy}${mm}${dd}${last}`;
  let sum = 0;
  for (let i = 0; i < 10; i++) {
    let d = Number(ten[i]) * (i % 2 === 0 ? 2 : 1);
    if (d > 9) d -= 9;
    sum += d;
  }
  if (sum % 10 !== 0) return null;
  const date = new Date(Date.UTC(Number(cent + yy), Number(mm) - 1, day));
  if (date.getUTCMonth() !== Number(mm) - 1 || date.getUTCDate() !== day) return null;
  return `${cent}${yy}${mm}${dd}-${last}`;
}

In practice, I've seen that getting the personnummer format right sidesteps a lot of problems. A format mismatch could mess with age calculations or even lead to rejecting valid entries.

Bug 3: Trusting Unseen User Inputs

Forms that change dynamically often hide fields based on previous answers. But here's the catch: stale or tampered requests can sneak phantom data into the system. To stop this, the server has to trim the request, only keeping visible fields and cleaning each value by its type:

export function prune(def: DocumentDef, raw: Record): Answers {
  const out: Answers = {};
  for (let pass = 0; pass < 2; pass++) {
    for (const step of def.steps) {
      if (!test(step.showIf, { top: out })) {
        for (const f of step.fields) delete out[f.id];
        continue;
      }
      for (const f of step.fields) {
        if (f.type === 'info') continue;
        const visible = pass === 0 || test(f.showIf, { top: out });
        if (!visible) { delete out[f.id]; continue; }
        out[f.id] = f.type === 'repeater'
          ? cleanItems(f, raw[f.id], out)
          : cleanValue(f, raw[f.id]);
      }
    }
  }
  return out;
}

Server-side checks like these are critical for keeping data accurate, especially in legal documents where precision is everything. I've found that they help a lot in maintaining trust and integrity.

Implementing These Lessons

If you want to implement these lessons, here are some steps to get you going:

  • Ensure all user input validation is rigorous, especially when legal compliance is a factor.
  • Use clear data models to guide user interaction and prevent invalid data entry.
  • Make sure your validation logic covers all bases, even tricky cases like coordination numbers in personnummer.
  • Test your form logic with varied data inputs to find and fix errors early on.

In my experience, testing with a wide range of data inputs will reveal hidden bugs and reinforce your application's reliability across different scenarios.

Limitations and Considerations

While these solutions tackle common issues, they're not perfect. Staying on top of legal changes is vital, as rules might change and require new validation updates. Educating users is crucial, too; no software can fully replace a user's understanding of legal guidelines. Also, complex rules might mean you need help from legal experts to ensure compliance.

Even with a slick form engine, users need clear instructions and warnings about possible disqualifications and the importance of accurate information. This can cut down on mistakes and make for a much smoother user experience.

Sources

Swedish will-witness rules in TypeScript: 4 bugs a form engine must prevent

Every claim above was checked against this source before publishing. The analysis, the code and the opinions are mine.

Frequently asked

What is the main challenge with Swedish wills?

Ensuring the witnesses meet eligibility criteria to prevent the will or its provisions from becoming void.

How does the form engine validate witness eligibility?

By using a data model of disqualifying relationships and comparing witness names against other names in the will.

What is a personnummer?

A Swedish personal identity number used to identify parties in legal documents, with specific formatting rules.

Why is pruning user input important?

To remove phantom data from unseen fields, ensuring only valid inputs affect document generation.

Deepak Kumar

Written by

Deepak Kumar

Sr Software Engineer at India Today Group | Aaj Tak · MERN Stack · Generative AI

I have worked in JavaScript daily for nine years, across browser code that has to stay fast and server code that has to stay up. I write here about what those systems actually do once real traffic hits them. Hiring a JavaScript developer in India? Here is how I work.

Message me