Jul 31st 2025

Langium 4.0 is released!

Benjamin F. WilsonBenjamin F. Wilson

We’re happy to announce the latest release of Langium 4.0! We’ve been hard at work on this release, and there are quite a lot of changes we want to share. Overall, the 4.0 release brings some significant improvements to the Langium framework, and some new features, namely:

  • Multi-reference support
  • Infix operator rules
  • Strict mode for Langium grammars

In addition to these new features there have also been improvements to the Yeoman generator for Langium projects, so that it now creates an npm workspace by default. Lastly, we’ve also made some significant improvements to how the ast.ts file is structured, making it easier to leverage TypeScript’s type system when performing tests and validations that depend on the meta-model of your language.

We continue to be impressed with the projects people are building with Langium, and the growing adoption of the framework. This release is a culmination of feedback from the community, and also our own experiences of running Langium through production-scale projects. Our goal remains to maintain & improve the ease of usage and performance of Langium across the board, and we see this release as building on that foundation. We’d like to thank everyone involved, and we look forward to seeing what you build with Langium!

Let’s dig into the features of Langium 4.0!

Multi-references

We’ve added new multi-reference support to Langium, such that references in your language can now target more than one element at the same time. Previously references were one-to-one, but now it’s possible to express multi-references like so:

Element: name=ID;

// Can reference multiple elements with the same name
ReferenceElement: elements=[+Element];

This will give you an elements: MultiReference<Element> field in your AST, allowing resolved elements to be iterated over as needed.

This enables native support for expressing a wide range of language features that were previously much more difficult to handle. A common example is being able to express interface merging behavior (such as in TypeScript), where multiple interfaces with the same name produce a single coherent interface type.

Infix Operators

Infix operator support has been something we’ve considered for some time, and in Langium 4.0 we’re happy to finally include this.

We’ve added an infix notation that makes it easier to describe these operators, as well as their precedence. For example, using infix notation it’s now much easier to express common binary expressions:

infix BinaryExpression on PrimaryExpression:
    '%' // <-- Highest precedence
    > '^'
    > '*' | '/'
    > '+' | '-';  // <-- Lowest precedence

PrimaryExpression infers Expression:
    '(' Expression ')' |
    {infer NumberLiteral} value=NUMBER;

This new notation provides a more concise way to express such operators, and is also easier to read & understand than the previous tree-rewriting action approach. With that being said, the previous approach is still supported, so you can continue to use that as is.

However, in addition to the new notation being more concise, it’s also more performant, typically bringing up to 50% faster parsing speeds for expressions compared to the tree-rewriting approach (just in case you needed another reason to justify switching over). And in case you are interested in learning more about performance improvements, you can check out the post we wrote up about general optimization of parser performance.

Strict Mode for Langium Grammars

Another feature we’ve considered for some time is the ability to specify a strict mode for Langium grammars. This means disallowing inferred types completely (ones that are derived from your parser rules), and requiring you to explicitly define all your types up front instead. In this release we’ve finally made this available, so you can now specify strict mode for your grammar in the langium-config.json like so:

{
    "validation": {
        "types": "strict"
    }
}

By using strict mode, you can ensure that your meta-model doesn’t change unexpectedly, and that your language’s structure remains consistent across changes in your grammar. This can help avoid hard-to-debug issues later on, and ensures language stability as your project evolves.

Workspace Generation via Yeoman

As mentioned earlier, the new Yeoman generator for this release constructs an npm workspace by default. This means that when you create a new Langium project using the Yeoman generator, your project will automatically be setup in the packages folder. This makes it easy to manage all your language related packages within a single project, and allows you to readily import implementation details from other packages in your workspace.

AST.TS Improvements

Numerous improvements have been made to the synthesized ast.ts file, which contains language specific information derived from your Langium grammar. For Langium 4.0, we improved the ast.ts by reducing boiler-plate code and adding more support for developers.

The most interesting changes are explained in detail within the requirements language example, if you want to learn more.

Additional AST Properties

Your language’s generated ast.ts will now provide constants for properties of types. If you want to customize the linking of an element in your tests (such as requirements), you no longer need to write the literal string "Test" and "requirements"; you can use the generated constants Test.$type and Test.requirements instead:

export class MyScopeProvider extends DefaultScopeProvider {
	override getScope(context: ReferenceInfo): Scope {
        if (context.container.$type === Test.$type && context.property === Test.requirements) {
            return ...
        }
        return super.getScope(context);
    }
}

We also generate additional meta-data about your language, reducing the need to write more language-independent boiler-plate code. In the requirements language, for example, you can use reflection.types.Test.properties.requirements to get some information about the Test.requirements property, among other helpful details.

Namespaces for Multi-Language Projects

For multi-language projects consisting of related languages with multiple *.langium files, your ast.ts now contains new definitions which represent each language distinctly. Reachable terminals, lists of types, and more are now generated for each language within its own namespace. To demonstrate, here’s a partial snippet of the namespace from the Requirements language:

export namespace Requirements {
    export const Terminals = {
        WS: /\s+/,
        ID: /[_a-zA-Z][\w_]*/
    };
	export type AstType = {
        Contact: Contact
        Environment: Environment
    }
}

Although each language has its own namespace, definitions for the whole project are still generated by combining the definitions of all component languages.

Better Type System Support

While these combined definitions work as before without breaking changes, they now enable developers to write more fine-grained code targeting a single language. For projects with multiple languages, validation checks can now be grouped by language:

const checks: ValidationChecks<RequirementsAndTestsAstType> = {
	Requirement: [
		validator.checkRequirementNameContainsANumber,
		validator.checkRequirementIsCoveredByATest
	],
	Test: ...
};

Following this, validations can naturally be narrowed to a specific language, like for ValidationChecks<Requirements.AstType> or ValidationChecks<Tests.AstType>. This is particularly useful for multi-language projects, where you might want to validate a specific language, and would like to trust the type system to flag any mixups.

While this example targets validations, these changes support other uses cases as well. Such as enforcing formatting rules for each AST node of a single language in a multi-language project.

And more!

There’s even more changes & improvements to this release, far more than we can fit into this blog post. For a full list of changes, please check out the Langium CHANGELOG directly to read more about them. This includes details on breaking changes as well.

Conclusion

Overall we’re very excited to share this release with everyone, and we’re looking forward to continued feedback from the community. Again, we’d like to thank everyone who contributed to this release, and for those who didn’t have the chance, we’re always happy to receive your feedback & contributions going forward. Beyond that, feel free to check out the Langium documentation for more information on how to get started with Langium, and to learn more about the framework itself. Happy language building!

About the Author

Benjamin F. Wilson

Benjamin F. Wilson

Ben is an eclectic language engineering enthusiast. Proficient in full-stack development and well-versed in type systems. He is passionate about software architecture, analysis, and employing effective solution patterns. When he’s not at work, you can find him building tools & electronics with recycled parts.