The {[K in XXX]: YYY} syntax is a mapped type, a special object type which iterates over the union members of keylike type expression XXX and uses a type parameter K for each one inside the YYY type expression. (Note, K represents a type, not a property key value, so by convention we use uppercase characters there). You can use K inside the YYY expression so that each key K in XXX can have a different value type, like {[K in "a" | "b"]: K} is equivalent to {a: "a", b: "b"}. Mapped type syntax is not generalizable or extendable; you can't put other properties in there like {[K in XXX]: YYY; somethingElse: string}, and you can't put them inside interface declarations, or do anything else with it. It is a syntax error to do so. In some sense, the curly braces { ... } are part of the syntax for mapped types, and even though they look like the curly braces in other object types, they don't act like them.
It's important not to confuse this syntax with the similar-looking {[k: XXX]: YYY} syntax for index signatures. Mapped types use the in keyword, while index signatures do not. Index signatures require a dummy key name identifier (k in the example here), which is not a type parameter. The dummy key name exists only inside the key and cannot be used in the YYY expression, so it does not allow you to assign different values for different keys in the XXX; index signatures do not iterate over the keys inside XXX in any way. As signatures, index signatures can be used in any object type alongside other properties, like {[k: XXX]: YYY; somethingElse: string}, as long as the other properties don't conflict with the index signature. They can be included in interface declarations. The curly braces are not part of the syntax for index signatures.
Your question is therefore: why can't you add other properties to a mapped type? why do the curly braces in mapped types not work the way they do in other object types?
There was an issue at microsoft/TypeScript#13573 asking this exact question. There doesn't seem to be a definitive answer, though. It seems like an unanticipated use case. For a while, it was possible that a pull request at microsoft/TypeScript#26797 would be merged into the language as part of an effort to unify mapped types and index signatures, but this never happened. A relevant comment in microsoft/TypeScript#45089 by a TS team member explains that at this point it's unlikely that anything will change here, because it opens up questions about how to deal with possibly conflicting types and generics:
There are weird implications of mixing mapped types and property declarations that are elegantly solved with intersections
I won't go into these explicitly here; you can look at the linked issue for more information.
So that is the answer to "why is it like this", as far as it goes. So what can you do instead? The above comment mentions intersections; you can merge the properties of two object types together via intersections, so {a: string} & {b: string} is essentially the same as {a: string; b: string}. So one way to write your object type is this:
type MyObjType =
{ [K in Category]: string } &
{ customKey1: string, customKey2: string };
You can't use intersections as interface types directly, but you are allowed to make interface extend other named types with statically known keys. Your {[K in Category]: string} has statically known keys because Category is not generic, but it's not a named type. You could either name it yourself and then extend it:
type CategoryProps = { [K in Category]: string };
interface MyObjType extends CategoryProps {
customKey1: string
customKey2: string
}
Or, you could use the Record<K, V> utility type and extend that without having to declare a new name:
interface MyObjType extends Record<Category, string> {
customKey1: string
customKey2: string
}
Finally, you could always go the other direction and use a mapped type for all your keys instead of trying to add them separately:
type MyObjType =
{ [K in Category | "customKey1" | "customKey2"]: string };
Playground link to code