diff --git a/config/config.sample.yaml b/config/config.sample.yaml
index ed97ed42640ea79345159f5c5868bc810c68778b..10f6d2fb97e7f9ca24fff97f962683894245c11b 100644
--- a/config/config.sample.yaml
+++ b/config/config.sample.yaml
@@ -89,3 +89,8 @@ limits:
     # (Copies of a sent message may arrive from discord before we've
     # fininished handling it, causing us to echo it back to the room)
     discordSendDelay: 750
+ghosts:
+    # Pattern for the ghosts nick, available is :nick, :username, :tag and :id
+    nickPattern: ":nick"
+    # Pattern for the ghosts username, available is :username, :tag and :id
+    usernamePattern: ":username#:tag"
diff --git a/config/config.schema.yaml b/config/config.schema.yaml
index 9a35f4b26cee167a0a94009d4614be1c70d127e9..7439ef2852c7af065ee69f2d91c8a476420b01da 100644
--- a/config/config.schema.yaml
+++ b/config/config.schema.yaml
@@ -109,3 +109,10 @@ properties:
                      type: "boolean"
                    ghostsLeave:
                      type: "boolean"
+    ghosts:
+        type: "object"
+        properties:
+            nickPattern:
+                type: "string"
+            usernamePattern:
+                type: "string"
diff --git a/src/channelsyncroniser.ts b/src/channelsyncroniser.ts
index 8f76d9e15799f57ed29e63e61a9fd53878cce47d..c948f35da71df85004efa18ee4fbb802d0014d85 100644
--- a/src/channelsyncroniser.ts
+++ b/src/channelsyncroniser.ts
@@ -163,14 +163,10 @@ export class ChannelSyncroniser {
             return channelState;
         }
 
-        const patternMap = {
+        const name: string = Util.ApplyPatternString(this.config.channel.namePattern, {
             guild: channel.guild.name,
             name: "#" + channel.name,
-        };
-        let name: string = this.config.channel.namePattern;
-        for (const p of Object.keys(patternMap)) {
-            name = name.replace(new RegExp(":" + p, "g"), patternMap[p]);
-        }
+        });
         const topic = channel.topic;
         const icon = channel.guild.icon;
         let iconUrl: string | null = null;
diff --git a/src/config.ts b/src/config.ts
index 3fdde89e4e9d5626a23654952bbfe7957a37484f..637bc555fb28450a2b2feccb5eb584eaa2c114fe 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -23,6 +23,7 @@ export class DiscordBridgeConfig {
     public room: DiscordBridgeConfigRoom = new DiscordBridgeConfigRoom();
     public channel: DiscordBridgeConfigChannel = new DiscordBridgeConfigChannel();
     public limits: DiscordBridgeConfigLimits = new DiscordBridgeConfigLimits();
+    public ghosts: DiscordBridgeConfigGhosts = new DiscordBridgeConfigGhosts();
 
     /**
      * Apply a set of keys and values over the default config.
@@ -109,3 +110,8 @@ export class LoggingFile {
     public enabled: string[] = [];
     public disabled: string[] = [];
 }
+
+class DiscordBridgeConfigGhosts {
+    public nickPattern: string = ":nick";
+    public usernamePattern: string = ":username#:tag";
+}
diff --git a/src/usersyncroniser.ts b/src/usersyncroniser.ts
index 3a329a3f1504f45ba8838181c17c817bfd135ead..86e144b3bb9d999128bf47c52408764b0fa8a956 100644
--- a/src/usersyncroniser.ts
+++ b/src/usersyncroniser.ts
@@ -234,7 +234,11 @@ export class UserSyncroniser {
             id: discordUser.id,
             mxUserId: `@_discord_${discordUser.id}${mxidExtra}:${this.config.bridge.domain}`,
         });
-        const displayName = this.displayNameForUser(discordUser);
+        const displayName = Util.ApplyPatternString(this.config.ghosts.usernamePattern, {
+            id: discordUser.id,
+            tag: discordUser.discriminator,
+            username: discordUser.username,
+        });
         // Determine if the user exists.
         const remoteId = discordUser.id + mxidExtra;
         const remoteUser = await this.userStore.getRemoteUser(remoteId);
@@ -270,10 +274,16 @@ export class UserSyncroniser {
     public async GetUserStateForGuildMember(
         newMember: GuildMember,
     ): Promise<IGuildMemberState> {
+        const name = Util.ApplyPatternString(this.config.ghosts.nickPattern, {
+            id: newMember.user.id,
+            nick: newMember.displayName,
+            tag: newMember.user.discriminator,
+            username: newMember.user.username,
+        });
         const guildState: IGuildMemberState = Object.assign({}, DEFAULT_GUILD_STATE, {
             bot: newMember.user.bot,
             displayColor: newMember.displayColor,
-            displayName: newMember.displayName,
+            displayName: name,
             id: newMember.id,
             mxUserId: `@_discord_${newMember.id}:${this.config.bridge.domain}`,
             roles: newMember.roles.map((role) => { return {
@@ -394,10 +404,6 @@ export class UserSyncroniser {
         });
     }
 
-    private displayNameForUser(discordUser): string {
-        return `${discordUser.username}#${discordUser.discriminator}`;
-    }
-
     private async leave(intent: Intent, roomId: string, checkCache: boolean = true) {
         const userId = intent.getClient().getUserId();
         if (checkCache && ![null, "join", "invite"]
diff --git a/src/util.ts b/src/util.ts
index 8b606bbf40b61d67562c244414ab7c42a7265b8f..aca4d651a2eb9780ff8d1a5666b389799edb10db 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -47,6 +47,10 @@ export interface ICommandParameters {
     [index: string]: ICommandParameter;
 }
 
+export interface IPatternMap {
+    [index: string]: string;
+}
+
 export class Util {
     /**
      * downloadFile - This function will take a URL and store the resulting data into
@@ -274,6 +278,13 @@ export class Util {
         const htmlColor = pad.substring(0, pad.length - colorHex.length) + colorHex;
         return htmlColor;
     }
+
+    public static ApplyPatternString(str: string, patternMap: IPatternMap): string {
+        for (const p of Object.keys(patternMap)) {
+            str = str.replace(new RegExp(":" + p, "g"), patternMap[p]);
+        }
+        return str;
+    }
 }
 
 interface IUploadResult {
diff --git a/test/test_channelsyncroniser.ts b/test/test_channelsyncroniser.ts
index c54aa2f1cd7e4ce0e728aa33428452351f95f691..7d90f6e827c782c1ca6f4ce11d618af0dd6ed124 100644
--- a/test/test_channelsyncroniser.ts
+++ b/test/test_channelsyncroniser.ts
@@ -24,6 +24,7 @@ import { MockGuild } from "./mocks/guild";
 import { MockMember } from "./mocks/member";
 import { MatrixEventProcessor, MatrixEventProcessorOpts } from "../src/matrixeventprocessor";
 import { DiscordBridgeConfig } from "../src/config";
+import { Util } from "../src/util";
 import { MockChannel } from "./mocks/channel";
 import { Bridge, MatrixRoom, RemoteRoom } from "matrix-appservice-bridge";
 // we are a test file and thus need those
@@ -44,6 +45,7 @@ let ROOM_DIRECTORY_VISIBILITY: any = null;
 const ChannelSync = (Proxyquire("../src/channelsyncroniser", {
     "./util": {
         Util: {
+            ApplyPatternString: Util.ApplyPatternString,
             UploadContentFromUrl: async () => {
                 UTIL_UPLOADED_AVATAR = true;
                 return {mxcUrl: "avatarset"};
diff --git a/test/test_usersyncroniser.ts b/test/test_usersyncroniser.ts
index c5b7b1cff0418e3f72494acf3194652745255b53..531f3e86e1ded68fb2848882d750c9e195f128b9 100644
--- a/test/test_usersyncroniser.ts
+++ b/test/test_usersyncroniser.ts
@@ -55,6 +55,7 @@ const GUILD_ROOM_IDS_WITH_ROLE = ["!abc:localhost", "!def:localhost"];
 const UserSync = (Proxyquire("../src/usersyncroniser", {
     "./util": {
         Util: {
+            ApplyPatternString: Util.ApplyPatternString,
             AsyncForEach: Util.AsyncForEach,
             UploadContentFromUrl: async () => {
                 UTIL_UPLOADED_AVATAR = true;
@@ -64,7 +65,7 @@ const UserSync = (Proxyquire("../src/usersyncroniser", {
     },
 })).UserSyncroniser;
 
-function CreateUserSync(remoteUsers: RemoteUser[] = []): UserSyncroniser {
+function CreateUserSync(remoteUsers: RemoteUser[] = [], ghostConfig: any = {}): UserSyncroniser {
     UTIL_UPLOADED_AVATAR = false;
     SEV_ROOM_ID = null;
     SEV_CONTENT = null;
@@ -153,6 +154,7 @@ function CreateUserSync(remoteUsers: RemoteUser[] = []): UserSyncroniser {
     };
     const config = new DiscordBridgeConfig();
     config.bridge.domain = "localhost";
+    config.ghosts = Object.assign({}, config.ghosts, ghostConfig);
     return new UserSync(bridge as Bridge, config, discordbot, userStore as any);
 }
 
@@ -196,6 +198,27 @@ describe("UserSyncroniser", () => {
             expect(state.avatarId, "AvatarID").is.empty;
             expect(state.avatarUrl, "AvatarUrl").is.null;
         });
+        it("Will obay name patterns", async () => {
+            const remoteUser = new RemoteUser("123456");
+            remoteUser.avatarurl = "test.jpg";
+            remoteUser.displayname = "TestUsername";
+
+            const userSync = CreateUserSync([remoteUser], {usernamePattern: ":username#:tag (Discord)"});
+            const user = new MockUser(
+                "123456",
+                "TestUsername",
+                "6969",
+                "test.jpg",
+                "111",
+            );
+            const state = await userSync.GetUserUpdateState(user as any);
+            expect(state.createUser, "CreateUser").is.false;
+            expect(state.removeAvatar, "RemoveAvatar").is.false;
+            expect(state.displayName, "DisplayName").equals("TestUsername#6969 (Discord)");
+            expect(state.mxUserId , "UserId").equals("@_discord_123456:localhost");
+            expect(state.avatarId, "AvatarID").is.empty;
+            expect(state.avatarUrl, "AvatarUrl").is.null;
+        });
         it("Will change avatars", async () => {
             const remoteUser = new RemoteUser("123456");
             remoteUser.avatarurl = "test.jpg";
@@ -467,6 +490,18 @@ describe("UserSyncroniser", () => {
             const state = await userSync.GetUserStateForGuildMember(member as any);
             expect(state.displayName).to.be.equal("BestDog");
         });
+        it("Will will obay nick pattern", async () => {
+            const userSync = CreateUserSync([new RemoteUser("123456")], { nickPattern: ":nick (Discord)" });
+            const guild = new MockGuild(
+                "654321");
+            const member = new MockMember(
+                "123456",
+                "username",
+                guild,
+                "BestDog");
+            const state = await userSync.GetUserStateForGuildMember(member as any);
+            expect(state.displayName).to.be.equal("BestDog (Discord)");
+        });
         it("Will correctly add roles", async () => {
             const userSync = CreateUserSync([new RemoteUser("123456")]);
             const guild = new MockGuild(
diff --git a/test/test_util.ts b/test/test_util.ts
index 3abf37a66dcc22ac4bee0d26631c08c63b5a764d..88380369b0ce0c8b620be061e30aa155aba227b4 100644
--- a/test/test_util.ts
+++ b/test/test_util.ts
@@ -158,4 +158,25 @@ describe("Util", () => {
             expect(reply).to.equal("#000000");
         });
     });
+    describe("ApplyPatternString", () => {
+        it("Should apply simple patterns", () => {
+            const reply = Util.ApplyPatternString(":name likes :animal", {
+                animal: "Foxies",
+                name: "Sorunome",
+            });
+            expect(reply).to.equal("Sorunome likes Foxies");
+        });
+        it("Should ignore unused tags", () => {
+            const reply = Util.ApplyPatternString(":name is :thing", {
+                name: "Sorunome",
+            });
+            expect(reply).to.equal("Sorunome is :thing");
+        });
+        it("Should do multi-replacements", () => {
+            const reply = Util.ApplyPatternString(":animal, :animal and :animal", {
+                animal: "fox",
+            });
+            expect(reply).to.equal("fox, fox and fox");
+        });
+    });
 });