Typing Svelte templates with TypeScript

Svelte's compile-time approach gives it a performance edge over many competitors, and its template syntax keeps component code approachable. Adding TypeScript to the mix tightens the safety net even further, letting you catch type errors in both your Svelte templates and your standalone .ts files before runtime. This guide covers a manual configuration that works whether you're retrofitting an existing project or want to understand what the official Svelte template does under the hood.

Baseline setup

Before TypeScript can understand Svelte components, you need a project where both technologies coexist. The starting point is a standard Svelte setup with webpack and a working tsconfig.json that targets modern JavaScript, uses Node resolution, and excludes node_modules:

{
  "compilerOptions": {
    "module": "esNext",
    "target": "esnext",
    "moduleResolution": "node"
  },
  "exclude": ["./node_modules"]
}

You also need a declaration file so TypeScript treats .svelte imports as valid modules:

declare module "*.svelte" {
  const value: any;
  export default value;
}

Finally, webpack needs a rule to route Svelte files through svelte-loader:

{
  test: /\.(html|svelte)$/,
  use: [
    { loader: "babel-loader" },
    {
      loader: "svelte-loader",
      options: {
        emitCss: true,
      },
    },
  ],
}

At this stage, TypeScript checking applies only to .ts files. You can verify it works by running a TypeScript watch task alongside webpack, then introducing a deliberate type error in index.ts:

let x: number = 12;

Change it to:

let x: number = "12";

The TypeScript watcher should immediately report the violation.

Enabling TypeScript inside components

With the basics in place, the next step is teaching Svelte to parse TypeScript within its <script> tags. Start by modifying a component like Helper.svelte, adding lang="ts" and annotating a prop:

<script lang="ts">
  export let val: number;
</script>

<h1>Value is: {val}</h1>

This will cause webpack to fail, since the loader doesn't yet know how to preprocess TypeScript. Install the necessary package:

npm i svelte-preprocess svelte-check --save

Then import svelte-preprocess in your webpack config and attach it to the Svelte rule:

const sveltePreprocess = require("svelte-preprocess");
{
  test: /\.(html|svelte)$/,
  use: [
    { loader: "babel-loader" },
    {
      loader: "svelte-loader",
      options: {
        emitCss: true,
        preprocess: sveltePreprocess({})
      },
    },
  ],
}

Restart webpack and the build should succeed again.

Adding type checking for templates

Compilation isn't the same as validation. If you pass an invalid prop value—say, a quoted number where a numeric prop is expected—the build will still pass unnoticed. For example, in App.svelte:

<Helper val={"3"} />

The standard tsc compiler won't catch this. Verification happens via the svelte-check utility, which runs in watch mode separately from tsc:

Showing the terminal with a caught error.

Remove the quotes around the value and the error clears:

Showing the same terminal window, but no errors.

In practice you'd run both watchers concurrently—svelte-check for templates and tsc for regular TypeScript files.

Stricter component-prop validation

One gap remains: omitting a required prop entirely doesn't trigger an error. Since val in Helper.svelte has no default value, it's mandatory:

<Helper /> // missing `val` prop

To make TypeScript flag this, enable additional checks in tsconfig.json:

"strict": true,
"noImplicitAny": false 

The first option turns on a suite of strictness checks that are off by default. The second, noImplicitAny: false, deliberately disables one of those strict checks—without it, any variable lacking an explicit type would become an error. Whether to keep that setting off is a matter of preference; many teams enforce noImplicitAny strictness, but it's not mandatory. With this configuration, restart svelte-check and the missing prop will now be reported:

Showing terminal with a caught error.

Limitations with dynamic prop access

There's a subtle catch with prop validation: once a component references either $$props or $$restProps, TypeScript immediately stops checking that component's props at all. Both constructs enable dynamic, undeclared prop access—common for UI libraries that pass arbitrary attributes through to DOM elements. The reasoning is that such patterns imply the component may accept props never explicitly declared, so static checking is no longer meaningful.

$$props also serves a narrower use case: accessing props whose names collide with reserved words like class. This pattern won't compile:

const className = $$props.class;
export let class = "";

There's a workaround that avoids the dynamic-access penalty. The same prop can be declared in a way that's valid:

let className;
export { className as class };

This lets you keep full type checking while still supporting the reserved-word prop name.

Practical takeaways

Getting TypeScript to fully cover Svelte components takes just a few moving parts: lang="ts" on your script tags, svelte-preprocess in the loader chain, and svelte-check as a watchdog. Used together, they bring early error detection to both your templates and your application logic, which is precisely where a typed language pays off.