diff --git a/src/main/java/org/jastadd/tooling/RelAstGrammarBlock.java b/src/main/java/org/jastadd/tooling/RelAstGrammarBlock.java
new file mode 100644
index 0000000000000000000000000000000000000000..7aca9c69078cb3d4426a34cb79a3f0bb9294bd76
--- /dev/null
+++ b/src/main/java/org/jastadd/tooling/RelAstGrammarBlock.java
@@ -0,0 +1,55 @@
+package org.jastadd.tooling;
+
+
+import com.intellij.formatting.*;
+import com.intellij.lang.ASTNode;
+import com.intellij.psi.TokenType;
+import com.intellij.psi.formatter.common.AbstractBlock;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class RelAstGrammarBlock extends AbstractBlock {
+
+  private final SpacingBuilder spacingBuilder;
+
+  protected RelAstGrammarBlock(@NotNull ASTNode node, @Nullable Wrap wrap, @Nullable Alignment alignment,
+                               SpacingBuilder spacingBuilder) {
+    super(node, wrap, alignment);
+    this.spacingBuilder = spacingBuilder;
+  }
+
+  @Override
+  protected List<Block> buildChildren() {
+    List<Block> blocks = new ArrayList<>();
+    ASTNode child = myNode.getFirstChildNode();
+    while (child != null) {
+      if (child.getElementType() != TokenType.WHITE_SPACE) {
+        Block block = new RelAstGrammarBlock(child, Wrap.createWrap(WrapType.NONE, false), Alignment.createAlignment(),
+          spacingBuilder);
+        blocks.add(block);
+      }
+      child = child.getTreeNext();
+    }
+    return blocks;
+  }
+
+  @Override
+  public Indent getIndent() {
+    return Indent.getNoneIndent();
+  }
+
+  @Nullable
+  @Override
+  public Spacing getSpacing(@Nullable Block child1, @NotNull Block child2) {
+    return spacingBuilder.getSpacing(this, child1, child2);
+  }
+
+  @Override
+  public boolean isLeaf() {
+    return myNode.getFirstChildNode() == null;
+  }
+
+}
diff --git a/src/main/java/org/jastadd/tooling/RelAstGrammarFormattingModelBuilder.java b/src/main/java/org/jastadd/tooling/RelAstGrammarFormattingModelBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..fc3a2efafb363b222d9c6fb24f10e9853599d2e9
--- /dev/null
+++ b/src/main/java/org/jastadd/tooling/RelAstGrammarFormattingModelBuilder.java
@@ -0,0 +1,59 @@
+package org.jastadd.tooling;
+
+import com.intellij.formatting.*;
+import com.intellij.lang.ASTNode;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.codeStyle.CodeStyleSettings;
+import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
+import com.intellij.psi.tree.TokenSet;
+import org.jastadd.tooling.parser.RelAstGrammarTypes;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class RelAstGrammarFormattingModelBuilder implements FormattingModelBuilder {
+
+  private static SpacingBuilder createSpaceBuilder(CodeStyleSettings settings) {
+    final CommonCodeStyleSettings commonSettings = settings.getCommonSettings(RelAstGrammar.INSTANCE.getID());
+
+    final TokenSet roleMultiplicityTokens = TokenSet.create(RelAstGrammarTypes.STAR, RelAstGrammarTypes.QUESTION_MARK);
+    final TokenSet relationDirectionTokens = TokenSet.create(RelAstGrammarTypes.LEFT, RelAstGrammarTypes.RIGHT, RelAstGrammarTypes.BIDIRECTIONAL);
+    final TokenSet declarationTokens = TokenSet.create(RelAstGrammarTypes.TYPE_DECL, RelAstGrammarTypes.RELATION);
+
+    return new SpacingBuilder(settings, RelAstGrammar.INSTANCE)
+      .around(RelAstGrammarTypes.ASSIGN).spaceIf(commonSettings.SPACE_AROUND_ASSIGNMENT_OPERATORS)
+      .before(declarationTokens).none()
+      .before(RelAstGrammarTypes.SCOL).spaceIf(commonSettings.SPACE_BEFORE_SEMICOLON)
+      .around(relationDirectionTokens).spaceIf(commonSettings.SPACE_AROUND_RELATIONAL_OPERATORS)
+      .between(RelAstGrammarTypes.COMPONENT, RelAstGrammarTypes.COMPONENT).spaces(1)
+      .around(RelAstGrammarTypes.DOT).none()
+      .before(roleMultiplicityTokens).spaceIf(commonSettings.SPACE_AROUND_UNARY_OPERATOR)
+      .beforeInside(RelAstGrammarTypes.COL, RelAstGrammarTypes.TYPE_DECL).spaceIf(commonSettings.SPACE_BEFORE_COLON)
+      .afterInside(RelAstGrammarTypes.COL, RelAstGrammarTypes.TYPE_DECL).spaceIf(commonSettings.SPACE_AFTER_COLON)
+      .aroundInside(RelAstGrammarTypes.COL, RelAstGrammarTypes.COMPONENT).none()
+      .withinPair(RelAstGrammarTypes.LT, RelAstGrammarTypes.GT).spaceIf(commonSettings.SPACE_WITHIN_CAST_PARENTHESES)
+      .withinPair(RelAstGrammarTypes.LBRACKET, RelAstGrammarTypes.RBRACKET).spaceIf(commonSettings.SPACE_WITHIN_BRACKETS)
+      .between(declarationTokens, RelAstGrammarTypes.COMMENT).spaces(1)
+      .afterInside(RelAstGrammarTypes.SLASH, RelAstGrammarTypes.NTA_COMPONENT).none()
+      .beforeInside(RelAstGrammarTypes.SLASH, RelAstGrammarTypes.NTA_COMPONENT).none();
+  }
+
+  @NotNull
+  @Override
+  public FormattingModel createModel(FormattingContext context) {
+    return FormattingModelProvider
+      .createFormattingModelForPsiFile(context.getPsiElement().getContainingFile(),
+        new RelAstGrammarBlock(context.getPsiElement().getNode(),
+          Wrap.createWrap(WrapType.NONE, false),
+          Alignment.createAlignment(),
+          createSpaceBuilder(context.getCodeStyleSettings())),
+        context.getCodeStyleSettings());
+  }
+
+  @Nullable
+  @Override
+  public TextRange getRangeAffectingIndent(PsiFile file, int offset, ASTNode elementAtOffset) {
+    return null;
+  }
+
+}
diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml
index 29eb7921f9f6a73367c02f9b3c68a98271a4911a..f591fa9116d3d4208895a73a5922b1ceff5ef111 100644
--- a/src/main/resources/META-INF/plugin.xml
+++ b/src/main/resources/META-INF/plugin.xml
@@ -45,6 +45,9 @@
 
         <lang.psiStructureViewFactory language="JastAddGrammar"
                                       implementationClass="org.jastadd.tooling.RelAstGrammarStructureViewFactory"/>
+
+        <lang.formatter language="JastAddGrammar"
+                        implementationClass="org.jastadd.tooling.RelAstGrammarFormattingModelBuilder"/>
     </extensions>
 
     <actions>