Customizing TS type generation

Is it possible to override / customize the TS types that Hilla generates for certain classes?

I’m working with MongoDB and want to send their com.bson.ObjectId type (essentially a DB document key) over the wire so I can use AutoCrud / AutoGrid / etc.

Currently Hilla generates an unusable TS type for this based on some internal fields within the class, whereas ideally I’d serialise / deserialise to String as needed.

interface ObjectId {
    timestamp: number;
    date?: string;
}
export default ObjectId;

I had this problem in the past when using SpringDoc, which generated the exact same OpenAPI types.

I was able to overcome this by customising the Jackson objectMapper to converts the object to/from a string during JSON conversion, along with a SpringDoc customization bean that instructed the OpenAPI generator to treat ObjectIds as Strings within the generated schema.

Is there something similar that I can do to customise the Hilla type generation for third-party types?

Hi @Mandeep.3,
Did you try to customize the default Jackson objectMapper as described in the docs?

Hi @rbrki07

Thanks for your reply - I’ve looked at that section of the docs, but not sure it’s possible to add the @JsonCreator and @JsonValue annotations as the class is a third-party type from a library.

I’ve created a custom Jackson module that registers serialisers and deserialisers for the ObjectId type, so the json conversion is handled correctly, but that isn’t picked up by the type generation code.

Could you please share how you registered the custom Jackson module?

I think it should be possible to register a custom module and Hilla should pick it up, but there have been situations in the past, where this was not the case and only the default mapper was taken into account.

If you don’t have to use the ObjectId, you could have a look at these examples for using AutoGrid and AutoCrud with MongoDB:

Sure, here is my Jackson module,

package pu.infra.config.components;

import org.springframework.stereotype.Component;

import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.FromStringDeserializer;
import com.fasterxml.jackson.databind.module.SimpleDeserializers;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.module.SimpleSerializers;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.bson.types.ObjectId;

@Component
public class PUJacksonModule extends SimpleModule {
  private PUJacksonModule() {
    super("PUJacksonModule");
  }

  @Override
  public void setupModule(SetupContext context) {
    SimpleSerializers serializers = new SimpleSerializers();
    serializers.addSerializer(ObjectId.class, ToStringSerializer.instance);
    context.addSerializers(serializers);

    SimpleDeserializers deserializers = new SimpleDeserializers();
    deserializers.addDeserializer(ObjectId.class, new FromStringDeserializer<ObjectId>(ObjectId.class) {
      @Override
      protected ObjectId _deserialize(String value, DeserializationContext ctxt) {
        return new ObjectId(value);
      }
    });

    context.addDeserializers(deserializers);

  }
}

The deserialiser isn’t actually required as ObjectId has a string-based constructor, but i hoped it would help type generation. I can confirm it’s being used, as I get id’s like the following in the FE when calling Hilla BE service endpoints,

{ "id": "688907dafab7f9f6e440e52a" ...}

whereas when it is disabled I get,

{ "id": { "timestamp": 1753789747, "date": "2025-07-29T11:49:07.000+00:00" }, ... }

So custom Jackson serialisation is working fine, but the generated TypeScript types are not.

I’m currently pretty keen to keep using ObjectId, as it acts as a domain type throughout the codebase. I could switch to String, but then it’s all too easy to mess up the contents of the string and create invalid id’s and introduce bugs.

Thanks for your blog posts, I actually used them as a reference in building a generic MongoCrudService. I’ve just uploaded them at Hilla MongoDB AutoCrud Support · GitHub.

I just created a simple example Hilla app and tried to understand the problem, but I’m still struggling. I actually used the code like in this blog post. It uses

@Document
public class Person {

    @Id
    private String id;

and it works fine. According to the Spring Data MongoDB docs, it is fine to use String, because Spring Data will take care of the mapping to and from ObjectId:

If possible, an id property or field declared as a String in the Java class is converted to and stored as an ObjectId by using a Spring Converter<String, ObjectId>. Valid conversion rules are delegated to the MongoDB Java driver. If it cannot be converted to an ObjectId, then the value is stored as a string in the database.

I can change my code to

@Document
public class Person {

    @MongoId
    private ObjectId id;

and I can see that updating existing documents always results in new documents. Is this one of the problem you have?

According to the Spring Boot docs about JSON, I added the following custom JsonComponent:

@JsonComponent
public class ObjectIdJsonComponent {

    public static class Serializer extends JsonObjectSerializer<ObjectId> {

        @Override
        protected void serializeObject(ObjectId value, JsonGenerator jgen, SerializerProvider provider)
                throws IOException {
            jgen.writeStringField("hexString", value.toHexString());
        }

    }

    public static class Deserializer extends JsonObjectDeserializer<ObjectId> {

        @Override
        protected ObjectId deserializeObject(JsonParser jsonParser, DeserializationContext context, ObjectCodec codec,
                JsonNode tree) throws IOException {
            String id = nullSafeValue(tree.get("hexString"), String.class);
            return new ObjectId(id);
        }

    }
}

With this in place, I can use ObjectId as type for the id field of the Person class and I can use it together with the AutoCrud component without problems/errors.

Thanks for you reply,

Yes, it is possible to use String as the id field and everything works and it’s supported by Spring Data MongoDB somewhat (assuming the value can be converted successfully).

However, doing that makes it really easy to introduce bugs in the data layer, such as if the String id value is set to a value that can not be converted to an ObjectId within Mongo. It’s much safer to use ObjectId directly as it acts as a “Domain Type” - i.e. it’s not possible to construct an invalid ObjectId.

I’ve tried adding the JsonComponent you provided, this works similar to the previous custom module I had, where ids are now converted to the following,

        "id": {
            "hexString": "6888b533117d16b0805b1c73"
        },

However the type problem still remains, Hilla still generates the following type for ObjectId,

interface ObjectId {
    timestamp: number;
    date?: string;
}

So you have the problem where the type definition of the data and the actual value of the data are not aligned. This makes working with ObjectId’s on the FE difficult, as all the generated code and endpoints expect the above type, but the actual data differs.

Ideally it would be possible to tell Hilla to always generate a string type definition for ObjectId when generating the OpenAPI types, similar to how SpringDoc does.

I’ve tried to replicate your example, I can get AutoGrid to work, however submitted data with AutoCrud / AutoForm fails for me for the above reason. I get a validation error from the FE, id.timestamp: must be a number, as the types are incorrect. (There may be a secondary issue here where Spring Data MongoDB @Id annotations aren’t marked are nullable within the generated model types).

I quickly created a simple repo to show the usage of MongoDB, ObjectId and AutoCrud: GitHub - rbrki07/hilla-mongodb-objectid-example.

The generation of the TypeScript interface is based on the only two public non-static getters in the ObjectId class, which are getTimestamp and getDate. I don’t know anything about the internal implementation of the TypeScript generator, but I assume it is quite alright to generate this kind of TypeScript interface based on this class, especially when you assume that the whole TypeScript generation process in Hilla is centered around the idea that you generate types for custom Entities or DTOs that you write in your Java backend - it’s opinionated.

That said, I don’t think there is a way to customize the generation of TypeScript types.

Thanks for the repo, that helps a lot!

Yes it doesn’t seem like there is an explicit way to customise type generation, however i’ve got something working that work for my case and may be helpful for others when wrapping external types in their entity models,

@Data
@EqualsAndHashCode(of = "id")
@AllArgsConstructor
@NoArgsConstructor
@Document
public class Note {
    @Id @JsonIgnore
    private ObjectId id;

    @JsonProperty("idx")
    public String getIdx() {
      return getId().toHexString();
    }

    @JsonProperty("idx")
    public void setIdx(@Nullable String id) {
      var id1 = id == null ? new ObjectId() : new ObjectId(id);
      this.setId(id1);
    }

  @Size(max = 256)
  private String title;

  private String description;

  @CreatedAt
  private Instant creationDate;
}

This results in the following typescript type definition,

interface Note {
    idx?: string;
    title: string;
    description: string;
    creationDate: string;
}
export default TestTask;

So the internal ObjectId id is not exposed to the FE, but creates a nullable string-based proxy idx which is converted as needed to the id field during Jackson deserialisation/serialisation.

This also works perfectly with “Auto Crud” / etc. using the itemIdProperty field set to idx (it’s also possible to use JsonProperty("id") but i prefer to use a separate field name):.