If your assistant writes a function for your juudd site as a .ts file, the deploy is refused before anything is stored, and the refusal says what to do: rename it to .js and remove the type annotations. Nothing else changes. A function is a .js or .mjs file, and that is the whole format.
This post is about why we refuse the file instead of accepting it, and what the refusal does not catch.
The check we expected to catch it did not
A function is a single ES module with no bare imports. We check the imports with a lexer, and we expected that gate to catch TypeScript for free. It does not, and it was never going to. A lexer finds imports. It has no opinion about the code between them.
We measured two cases. A handler written as async fetch(request: Request) parses without complaint and reports no imports. And import type { Foo } from './foo.js' comes back as an ordinary import.
So for a while a .ts function passed every check the platform had and then failed on the host that runs it, with a message written for somebody debugging a server, not for somebody who wrote the wrong file format. The failure was legal everywhere we looked and unreadable where it surfaced.
The refusal looks at the name first
The check runs before the lexer, and before the file's source is read. A .ts file is never parsed as JavaScript, so it never produces a syntax complaint about a line the author did not write. There are three answers:
.ts,.tsx,.mtsand.ctsget: this platform deploys plain ES modules, not TypeScript. Rename it to the same name ending in.jsand remove the type annotations; nothing else changes..jsxgets: JSX is not ES module syntax and nothing here compiles it. A function returns a Response, not an element, so build the response as a string or with template literals.- Anything else gets: a function must be a
.jsor.mjsfile.
Each one names the file, says what is wrong, and says the alternative. The tool's description also says plain JavaScript only, with no build step, so in the common case the refusal never fires at all.
Why not just strip the types
A transform that only removes types would make the assistant's first attempt work. It would not resolve anything, so it would not reopen the door we closed by refusing a bundler.
We said no anyway, for a reason about what a deploy is. What is stored is what is served. That sentence has to stay literally true, and a build step inside the deploy path is the one place it would stop being true. The refusal costs the caller one turn and a rename. Accepting the format would cost a dependency and a step between your files and your site.
Node's own type stripping was not an option either. It loads files; it does not hand back source we could store.
What the check does not catch
It reads the extension, not the source. A .js file with type annotations inside it still gets through this check and still fails when it runs. We accepted that knowingly. The extension is the thing an assistant gets wrong. Annotations under a .js name are something almost nobody writes by accident.
Two smaller consequences follow. The tool description now carries a claim about the format, which makes it one more sentence that must not go stale, because a client keeps that text from the moment it connects. And we have not measured whether callers actually lose a turn to this refusal rather than reading the description first. If they do, the transform is worth another look. That is an observation we do not have yet.
The rule, once
A function is plain JavaScript: a .js or .mjs file, and anything else is refused by name, before its source is read, with the rename that fixes it. The tools and their limits are listed in the docs.