diff --git a/index.js b/index.js
index 2be7e8367a8ba94fea34955c4e5082f4810122b8..9768d87813bc3e67105e1d97768f468ab13ddcb7 100644
--- a/index.js
+++ b/index.js
@@ -1,4 +1,5 @@
 const express = require('express');
+const fs = require('fs');
 const session = require('express-session');
 const bodyParser = require('body-parser');
 const mongoose = require('mongoose');
@@ -6,7 +7,7 @@ const morgan = require('morgan');
 const mongoDBStore = require('connect-mongodb-session')(session);
 
 // Config
-const config = require('./config.json');
+const config = require('js-yaml').safeLoad(fs.readFileSync('./config.yml', 'utf8'));
 // Middlewares
 const userLoader = require('./middlewares/userLoader');
 const loginChecker = require('./middlewares/loginChecker');
@@ -15,6 +16,7 @@ const render = require('./utils/render');
 const { warn, error } = require('./utils/notifications');
 // Routes
 const authRouter = require('./routes/auth');
+const charactersRouter = require('./routes/characters');
 
 // Configuration
 const app = express();
@@ -36,14 +38,16 @@ app.use(session({
 
 // Middlewares
 app.use(bodyParser.urlencoded({ extended: false }));
+app.use('/media', express.static('media'));
 app.use(userLoader);
 app.use(loginChecker);
 
 // Routes
-app.get('/', (req, res) => {
+app.get('/', async (req, res) => {
   return render(req, res, 'home');
 });
 app.use('/auth', authRouter);
+app.use('/characters', charactersRouter);
 
 mongoose.connect('mongodb://localhost/rolegame', err => {
   if (err) {
diff --git a/media/script.js b/media/script.js
new file mode 100644
index 0000000000000000000000000000000000000000..1ab47a6f37fdeba543778a8ee9466ab7478a0277
--- /dev/null
+++ b/media/script.js
@@ -0,0 +1,68 @@
+const changeAvatar = (avatar) => {
+  $('#avatar').attr('src', `/media/avatars/${avatar}`);
+};
+
+const attributes = {
+  'strength': 10,
+  'constitution': 10,
+  'dexterity': 10,
+  'intelligence': 10,
+  'wisdom': 10,
+  'charisma': 10
+};
+
+const costTable = {
+  7: -4,
+  8: -2,
+  9: -1,
+  10: 0,
+  11: 1,
+  12: 2,
+  13: 3,
+  14: 5,
+  15: 7,
+  16: 10,
+  17: 13,
+  18: 17
+};
+
+const getScore = () => Object.keys(attributes).reduce((total, el) => total + costTable[attributes[el]], 0);
+
+const reduceAttribute = (attribute) => {
+  attributes[attribute] --;
+  if (attributes[attribute]<7) {
+    attributes[attribute] ++;
+    alert("You can't have attribute under 7.");
+  } else {
+    document.getElementById(attribute).innerText = attributes[attribute];
+    $(`#${attribute}-bar`).progress('decrement', 1);
+    document.getElementById('available-points').innerHTML = initAttributesPoints - getScore();
+  }
+};
+
+const augmentAttribute = (attribute) => {
+  attributes[attribute] ++;
+  if (getScore()>initAttributesPoints) {
+    attributes[attribute] --;
+    alert("Not enough points.");
+  } else if (attributes[attribute]>18) {
+    alert("You can't have attribute over 18.");
+    attributes[attribute] --;
+  } else {
+    document.getElementById(attribute).innerText = attributes[attribute];
+    $(`#${attribute}-bar`).progress('increment', 1);
+    document.getElementById('available-points').innerHTML = initAttributesPoints - getScore();
+  }
+};
+
+const createCharacter = () => {
+  const form = document.getElementById('create-character');
+  Object.keys(attributes).forEach(attribute => {
+    const input = document.createElement("input");
+    input.setAttribute("name", attribute);
+    input.setAttribute("value", attributes[attribute]);
+    input.setAttribute("type", "hidden");
+    form.appendChild(input);
+  });
+  form.submit();
+};
diff --git a/models/character.js b/models/character.js
index 95853d2b3d31cb6259b7101a57e0af77485c918a..942d5eea18b18e231936a2041f3f8e048511448c 100644
--- a/models/character.js
+++ b/models/character.js
@@ -1,8 +1,54 @@
 const mongoose = require('mongoose');
+const random = require('random');
+const fs = require('fs');
+const { maxActionPoints } = require('js-yaml').safeLoad(fs.readFileSync('./config.yml', 'utf8'));
 
 const Character = new mongoose.Schema({
-  name: String,
-  level: Number,
+  user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
+  name: { type: String, require: true },
+  avatar: { type: String },
+  birthDate: { type: Date, required: true, default: Date.now },
+  actionPoints: { type: Number, default: maxActionPoints, min: 0, max: maxActionPoints },
+  attributes: {
+    strength: { type: Number, default: 10 },
+    constitution: { type: Number, default: 10 },
+    dexterity: { type: Number, default: 10 },
+    intelligence: { type: Number, default: 10 },
+    wisdom: { type: Number, default: 10 },
+    charisma: { type: Number, default: 10 },
+  }
 });
 
+function randomAttribute() {
+  let dices = [];
+  let min = random.int(1,6);
+  for (let i = 0; i < 3; i++) {
+    let number = random.int(1,6);
+    if (number < min) {
+      dices.push(min);
+      min = number;
+    } else {
+      dices.push(number);
+    }
+  }
+  return dices.reduce((total, val) => total + val);
+}
+
+// Character.statics.generateCharacter = async function(name, userId, avatar) {
+//   const character = await this.create({
+//     name,
+//     user: userId,
+//     avatar,
+//     attributes: {
+//       strength: randomAttribute(),
+//       constitution: randomAttribute(),
+//       dexterity: randomAttribute(),
+//       intelligence: randomAttribute(),
+//       wisdom: randomAttribute(),
+//       charisma: randomAttribute(),
+//     }
+//   });
+//   return character;
+// }
+
 module.exports = mongoose.model('Character', Character);
\ No newline at end of file
diff --git a/routes/characters.js b/routes/characters.js
new file mode 100644
index 0000000000000000000000000000000000000000..2678e67d44c68c01fd450215fd74c9b2e9906de4
--- /dev/null
+++ b/routes/characters.js
@@ -0,0 +1,48 @@
+const express = require('express');
+const fs = require('fs');
+const { initAttributesPoints, costTable, attributes, maxActionPoints, maxCharactersNumber } = require('js-yaml').safeLoad(fs.readFileSync('./config.yml', 'utf8'));
+const Character = require('../models/character');
+const render = require('../utils/render');
+const { warn, error } = require('../utils/notifications');
+
+const router = express.Router();
+
+const getScore = (data) => Object.keys(data).reduce((total, el) => total + costTable[data[el]], 0);
+
+router.get('/', async (req, res) => {
+  // TODO: Login required
+  const avatars = fs.readdirSync('media/avatars');
+  Character.find({ user: req.session.user._id }, (err, characters) => {
+    return render(req, res, 'characters', { characters, avatars, initAttributesPoints, maxActionPoints });
+  });
+});
+
+// router.post('/generate', async (req, res) => {
+//   // TODO: Login required
+//   if (!req.body.name) {
+//     return error(req, res, 'Name required');
+//   }
+//   const character = await Character.generateCharacter(req.body.name, req.session.user._id, req.body.avatar);
+//   return res.redirect('/characters');
+// });
+
+router.post('/create', async (req, res) => {
+  const validatedAttributes = attributes.reduce((obj, name) => {
+      obj[name] = Number(req.body[name]);
+      return obj;
+    }, {});
+  if (getScore(validatedAttributes) > initAttributesPoints || attributes.map(name => validatedAttributes[name]).some(value => value > 18 || value < 7)) {
+    return error(req, res, 'Bad form', "Character creation corrupted");
+  } else if (maxCharactersNumber <= await Character.countDocuments({ user: req.session.user._id })) {
+    return error(req, res, 'Too many characters', "You are not allowed to create one more character.");
+  }
+  await Character.create({
+    user: req.session.user._id,
+    name: req.body.name,
+    avatar: req.body.avatar,
+    attributes: validatedAttributes
+  });
+  res.redirect('/characters');
+});
+
+module.exports = router;
\ No newline at end of file
diff --git a/views/character_summary.pug b/views/character_summary.pug
new file mode 100644
index 0000000000000000000000000000000000000000..ced5e41031650bf8d49403f01e394c6cbdf71e7d
--- /dev/null
+++ b/views/character_summary.pug
@@ -0,0 +1,28 @@
+.ui.card.fluid
+  .content
+    img.right.floated.mini.image.ui(src="/media/avatars/"+character.avatar, alt="Avatar")
+    .header= character.name
+    .meta
+      .date Created on #{character.birthDate.toDateString()}
+    .ui.tiny.progress.yellow.progress.action-points(data-total=maxActionPoints, data-value=character.actionPoints)
+      .bar
+      .label #{character.actionPoints} action points
+  .content
+    .ui.label(class=character.attributes.strength<=8 ? "orange" : character.attributes.strength>=12 ? "green" : "" )
+      | Strength
+      .detail= character.attributes.strength
+    .ui.label(class=character.attributes.constitution<=8 ? "orange" : character.attributes.constitution>=12 ? "green" : "" )
+      | Constitution
+      .detail= character.attributes.constitution
+    .ui.label(class=character.attributes.dexterity<=8 ? "orange" : character.attributes.dexterity>=12 ? "green" : "" )
+      | Dexterity
+      .detail= character.attributes.dexterity
+    .ui.label(class=character.attributes.intelligence<=8 ? "orange" : character.attributes.intelligence>=12 ? "green" : "" )
+      | Intelligence
+      .detail= character.attributes.intelligence
+    .ui.label(class=character.attributes.wisdom<=8 ? "orange" : character.attributes.wisdom>=12 ? "green" : "" )
+      | Wisdom
+      .detail= character.attributes.wisdom
+    .ui.label(class=character.attributes.charisma<=8 ? "orange" : character.attributes.charisma>=12 ? "green" : "" )
+      | Charisma
+      .detail= character.attributes.charisma
diff --git a/views/characters.pug b/views/characters.pug
new file mode 100644
index 0000000000000000000000000000000000000000..9f44b9a428b362841c871a3491154c6afd5f4c4d
--- /dev/null
+++ b/views/characters.pug
@@ -0,0 +1,56 @@
+extends base.pug
+
+block main
+  .ui.cards
+    each character in characters
+      include character_summary.pug
+  button.ui.basic.teal.labeled.icon.button(onClick="$('.ui.modal').modal('show');")
+    | Create character
+    i.add.icon
+
+  .ui.modal
+    i.close.icon
+    .ui.header Create a new character
+    .ui.image.content
+      .ui.medium.image
+        img#avatar(src="/media/avatars/fazzlemare_by_charro_art_d8etbfa-pre.jpg", alt="Avatar")
+      .description
+        form.ui.form#create-character(name="create-character", action="/characters/create", method="post")
+          .ui.header Details
+          .ui.field
+            input#name(type="text", name="name", placeholder="name")
+          .ui.field
+            select#avatar-select.ui.selection.dropdown(name="avatar", onChange="changeAvatar($('select').dropdown('get value'))")
+              each avatar in avatars
+                option(value=avatar)= avatar
+          .ui.header Attributes:&nbsp
+            span#available-points= initAttributesPoints
+            | &nbsppoints remaining
+          .ui.grid
+            each attribute in ['strength', 'constitution', 'dexterity', 'intelligence', 'wisdom', 'charisma']
+              .eight.wide.column
+                .ui.segment
+                  .ui.horizontal.statistic
+                    .value(id=attribute) 10
+                    .label= attribute
+                  button.ui.button.orange.icon.circular.left.floated(type="button", onClick=`reduceAttribute('${attribute}')`)
+                    i.minus.icon
+                  button.ui.button.teal.icon.circular.right.floated(type="button", onClick=`augmentAttribute('${attribute}')`)
+                    i.add.icon
+                  .ui.bottom.attached.progress(id=attribute+"-bar")
+                    .bar
+            
+
+    .actions
+      button.ui.basic.orange.button.labeled.icon(type="reset", onClick="$('.ui.modal').modal('hide');")
+        i.close.icon
+        | Cancel
+      button.ui.teal.labeled.icon.button(type="submit", onClick="createCharacter()")
+        i.check.icon
+        | Create
+
+  
+  script $('.ui.dropdown').dropdown();
+  script $('.ui.progress').progress({ total: 20, value: 10 });
+  script let initAttributesPoints = #{initAttributesPoints};
+  script(src="/media/script.js")
\ No newline at end of file