diff --git a/cypress/integration/basics.js b/cypress/integration/basics.js
index 4e1e575..8456740 100644
--- a/cypress/integration/basics.js
+++ b/cypress/integration/basics.js
@@ -1,267 +1,335 @@
 /**
  * Copyright (C) 2020  The Software Heritage developers
  * See the AUTHORS file at the top-level directory of this distribution
  * License: GNU Affero General Public License version 3, or any later version
  * See top-level LICENSE file for more information
  */
 
 /*
  * Tests the basic features of the application.
  */
 
 "use strict";
 
 describe('JSON Generation', function() {
     beforeEach(function() {
         /* Clear the session storage, as it is used to restore field data;
          * and we don't want a test to load data from the previous test. */
         cy.window().then((win) => {
             win.sessionStorage.clear()
         })
         cy.visit('./index.html');
     });
 
     it('works just from the software name', function() {
         cy.get('#name').type('My Test Software');
         cy.get('#generateCodemetaV2').click();
 
         cy.get('#errorMessage').should('have.text', '');
         cy.get('#codemetaText').then((elem) => JSON.parse(elem.text()))
             .should('deep.equal', {
                 "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "type": "SoftwareSourceCode",
                 "name": "My Test Software",
         });
     });
 
     it('works just from all main fields when using only one license', function() {
         cy.get('#name').type('My Test Software');
         cy.get('#description').type('This is a\ngreat piece of software');
         cy.get('#dateCreated').type('2019-10-02');
         cy.get('#datePublished').type('2020-01-01');
         cy.get('#license').type('AGPL-3.0');
         cy.get("#license").type('{enter}');
         cy.get('#generateCodemetaV2').click();
 
         cy.get("#license").should('have.value', '');
         cy.get('#errorMessage').should('have.text', '');
         cy.get('#codemetaText').then((elem) => JSON.parse(elem.text()))
             .should('deep.equal', {
                 "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "type": "SoftwareSourceCode",
                 "license": "https://spdx.org/licenses/AGPL-3.0",
                 "dateCreated": "2019-10-02",
                 "datePublished": "2020-01-01",
                 "name": "My Test Software",
                 "description": "This is a\ngreat piece of software",
         });
     });
 
     it('works just from all main fields when using multiple licenses', function() {
         cy.get('#name').type('My Test Software');
         cy.get('#description').type('This is a\ngreat piece of software');
         cy.get('#dateCreated').type('2019-10-02');
         cy.get('#datePublished').type('2020-01-01');
         cy.get('#license').type('AGPL-3.0');
         cy.get("#license").type('{enter}');
         cy.get('#license').type('MIT');
         cy.get("#license").type('{enter}');
 
         cy.get('#generateCodemetaV2').click();
 
         cy.get("#license").should('have.value', '');
         cy.get('#errorMessage').should('have.text', '');
         cy.get('#codemetaText').then((elem) => JSON.parse(elem.text()))
             .should('deep.equal', {
                 "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "type": "SoftwareSourceCode",
                 "license": ["https://spdx.org/licenses/AGPL-3.0", "https://spdx.org/licenses/MIT"],
                 "dateCreated": "2019-10-02",
                 "datePublished": "2020-01-01",
                 "name": "My Test Software",
                 "description": "This is a\ngreat piece of software",
         });
     });
 
     it('works when choosing licenses without the keyboard', function() {
         cy.get('#name').type('My Test Software');
         cy.get('#description').type('This is a\ngreat piece of software');
         cy.get('#dateCreated').type('2019-10-02');
         cy.get('#datePublished').type('2020-01-01');
         cy.get('#license').type('AGPL-3.0');
         // no cy.get("#license").type('{enter}'); here
         cy.get('#generateCodemetaV2').click();
 
         cy.get("#license").should('have.value', '');
         cy.get('#errorMessage').should('have.text', '');
         cy.get('#codemetaText').then((elem) => JSON.parse(elem.text()))
             .should('deep.equal', {
                 "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "type": "SoftwareSourceCode",
                 "license": "https://spdx.org/licenses/AGPL-3.0",
                 "dateCreated": "2019-10-02",
                 "datePublished": "2020-01-01",
                 "name": "My Test Software",
                 "description": "This is a\ngreat piece of software",
         });
     });
 
     it('works for new codemeta terms in both versions', function() {
         cy.get('#name').type('My Test Software');
         cy.get('#contIntegration').type('https://test-ci.org/my-software');
         cy.get('#isSourceCodeOf').type('Bigger Application');
         cy.get('#reviewAspect').type('Some software aspect');
         cy.get('#reviewBody').type('Some review');
 
         cy.get('#generateCodemetaV2').click();
         cy.get('#errorMessage').should('have.text', '');
         cy.get('#codemetaText').then((elem) => JSON.parse(elem.text()))
             .should('deep.equal', {
                 "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "type": "SoftwareSourceCode",
                 "name": "My Test Software",
                 "contIntegration": "https://test-ci.org/my-software",
                 "codemeta:continuousIntegration": {
                     "id": "https://test-ci.org/my-software"
                 },
                 "codemeta:isSourceCodeOf": {
                     "id": "Bigger Application"
                 },
                 "schema:review": {
                     "type": "schema:Review",
                     "schema:reviewAspect": "Some software aspect",
                     "schema:reviewBody": "Some review"
                 }
         });
 
         cy.get('#generateCodemetaV3').click();
         cy.get('#errorMessage').should('have.text', '');
         cy.get('#codemetaText').then((elem) => JSON.parse(elem.text()))
             .should('deep.equal', {
                 "@context": "https://w3id.org/codemeta/3.0",
                 "type": "SoftwareSourceCode",
                 "name": "My Test Software",
                 "continuousIntegration": "https://test-ci.org/my-software",
                 "codemeta:contIntegration": {
                     "id": "https://test-ci.org/my-software"
                 },
                 "isSourceCodeOf": "Bigger Application",
                 "review": {
                     "type": "Review",
                     "reviewAspect": "Some software aspect",
                     "reviewBody": "Some review"
                 }
         });
     });
 });
 
 describe('JSON Import', function() {
     it('works just from the software name', function() {
         cy.get('#codemetaText').then((elem) =>
             elem.text(JSON.stringify({
                 "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "@type": "SoftwareSourceCode",
                 "name": "My Test Software",
             }))
         );
         cy.get('#importCodemeta').click();
 
         cy.get('#name').should('have.value', 'My Test Software');
     });
 
     it('works just from all main fields when using license as string', function() {
         cy.get('#codemetaText').then((elem) =>
             elem.text(JSON.stringify({
                 "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "@type": "SoftwareSourceCode",
                 "license": "https://spdx.org/licenses/AGPL-3.0",
                 "dateCreated": "2019-10-02",
                 "datePublished": "2020-01-01",
                 "name": "My Test Software",
                 "description": "This is a\ngreat piece of software",
             }))
         );
         cy.get('#importCodemeta').click();
 
         cy.get('#name').should('have.value', 'My Test Software');
         cy.get('#description').should('have.value', 'This is a\ngreat piece of software');
         cy.get('#dateCreated').should('have.value', '2019-10-02');
         cy.get('#datePublished').should('have.value', '2020-01-01');
         cy.get('#license').should('have.value', '');
         cy.get("#selected-licenses").children().should('have.length', 1);
         cy.get("#selected-licenses").children().first().children().first().should('have.text', 'AGPL-3.0');
     });
 
     it('works just from all main fields when using license as array', function() {
         cy.get('#codemetaText').then((elem) =>
             elem.text(JSON.stringify({
                 "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "@type": "SoftwareSourceCode",
                 "license": ["https://spdx.org/licenses/AGPL-3.0", "https://spdx.org/licenses/MIT"],
                 "dateCreated": "2019-10-02",
                 "datePublished": "2020-01-01",
                 "name": "My Test Software",
                 "description": "This is a\ngreat piece of software",
             }))
         );
         cy.get('#importCodemeta').click();
 
         cy.get('#name').should('have.value', 'My Test Software');
         cy.get('#description').should('have.value', 'This is a\ngreat piece of software');
         cy.get('#dateCreated').should('have.value', '2019-10-02');
         cy.get('#datePublished').should('have.value', '2020-01-01');
         cy.get('#license').should('have.value', '');
         cy.get("#selected-licenses").children().should('have.length', 2);
         cy.get("#selected-licenses").children().eq(0).children().first().should('have.text', 'AGPL-3.0');
         cy.get("#selected-licenses").children().eq(1).children().first().should('have.text', 'MIT');
     });
 
     it('works with expanded document version', function () {
         cy.get('#codemetaText').then((elem) =>
             elem.text(JSON.stringify({
+                "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "http://schema.org/name": [
                     {
                         "@value": "My Test Software"
                     }
                 ],
                 "@type": [
                     "http://schema.org/SoftwareSourceCode"
                 ]
             }))
         );
         cy.get('#importCodemeta').click();
 
         cy.get('#name').should('have.value', 'My Test Software');
     });
 
     it('errors on invalid type', function() {
         cy.get('#codemetaText').then((elem) =>
             elem.text(JSON.stringify({
                 "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "@type": "foo",
                 "name": "My Test Software",
             }))
         );
         cy.get('#importCodemeta').click();
 
         // Should still be imported as much as possible
         cy.get('#name').should('have.value', 'My Test Software');
 
         // But must display an error
         cy.get('#errorMessage').should('have.text', 'Wrong document type: must be "SoftwareSourceCode"/"SoftwareApplication", not "foo"');
     });
 
     it('allows singleton array as context', function() {
         cy.get('#codemetaText').then((elem) =>
             elem.text(JSON.stringify({
                 "@context": ["https://doi.org/10.5063/schema/codemeta-2.0"],
                 "@type": "SoftwareSourceCode",
                 "name": "My Test Software",
             }))
         );
         cy.get('#importCodemeta').click();
 
         cy.get('#name').should('have.value', 'My Test Software');
     });
 
+    it('works for new codemeta v3.0 terms', function() {
+        cy.get('#codemetaText').then((elem) =>
+            elem.text(JSON.stringify({
+                "@context": "https://w3id.org/codemeta/3.0",
+                "type": "SoftwareSourceCode",
+                "name": "My Test Software",
+                "continuousIntegration": "https://test-ci.org/my-software",
+                "isSourceCodeOf": "Bigger Application",
+                "review": {
+                    "type": "Review",
+                    "reviewAspect": "Some software aspect",
+                    "reviewBody": "Some review"
+                }
+            }))
+        );
+        cy.get('#importCodemeta').click();
+
+        cy.get('#contIntegration').should('have.value', 'https://test-ci.org/my-software');
+        cy.get('#isSourceCodeOf').should('have.value', 'Bigger Application');
+        cy.get('#reviewAspect').should('have.value', 'Some software aspect');
+        cy.get('#reviewBody').should('have.value', 'Some review');
+    });
+
+    it('works for codemeta v2.0 terms in v3.0 version', function() {
+        cy.get('#codemetaText').then((elem) =>
+            elem.text(JSON.stringify({
+                "@context": "https://w3id.org/codemeta/3.0",
+                "type": "SoftwareSourceCode",
+                "name": "My Test Software",
+                "continuousIntegration": "https://test-ci.org/my-software",
+                "codemeta:contIntegration": {
+                    "id": "https://test-ci.org/my-software"
+                },
+            }))
+        );
+        cy.get('#importCodemeta').click();
+
+        cy.get('#contIntegration').should('have.value', 'https://test-ci.org/my-software');
+    });
+
+    it('works for codemeta v3.0 terms in v2.0 version, and does not work for new terms', function() {
+        cy.get('#codemetaText').then((elem) =>
+            elem.text(JSON.stringify({
+                "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
+                "type": "SoftwareSourceCode",
+                "name": "My Test Software",
+                "contIntegration": "https://test-ci.org/my-software",
+                "codemeta:continuousIntegration": {
+                    "id": "https://test-ci.org/my-software"
+                },
+                "codemeta:isSourceCodeOf": {
+                    "id": "Bigger Application"
+                },
+                "schema:review": {
+                    "type": "schema:Review",
+                    "schema:reviewAspect": "Some software aspect",
+                    "schema:reviewBody": "Some review"
+                }
+            }))
+        );
+        cy.get('#importCodemeta').click();
+
+        cy.get('#contIntegration').should('have.value', 'https://test-ci.org/my-software');
+        cy.get('#isSourceCodeOf').should('have.value', '');
+        cy.get('#reviewAspect').should('have.value', '');
+        cy.get('#reviewBody').should('have.value', '');
+    });
 });
diff --git a/cypress/integration/persons.js b/cypress/integration/persons.js
index 7516167..232a5aa 100644
--- a/cypress/integration/persons.js
+++ b/cypress/integration/persons.js
@@ -1,686 +1,829 @@
 /**
  * Copyright (C) 2020  The Software Heritage developers
  * See the AUTHORS file at the top-level directory of this distribution
  * License: GNU Affero General Public License version 3, or any later version
  * See top-level LICENSE file for more information
  */
 
 /*
  * Tests the author/contributor dynamic fieldsets
  */
 
 "use strict";
 
 describe('Zero author', function() {
     it('can be exported', function() {
         cy.get('#name').type('My Test Software');
         cy.get('#generateCodemetaV2').click();
 
         cy.get('#errorMessage').should('have.text', '');
         cy.get('#codemetaText').then((elem) => JSON.parse(elem.text()))
             .should('deep.equal', {
                 "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "type": "SoftwareSourceCode",
                 "name": "My Test Software",
         });
     });
 
     it('can be imported from no list', function() {
         cy.get('#codemetaText').then((elem) =>
             elem.text(JSON.stringify({
                 "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "@type": "SoftwareSourceCode",
                 "name": "My Test Software",
             }))
         );
         cy.get('#importCodemeta').click();
 
         cy.get('#author_nb').should('have.value', '0');
         cy.get('#author_0').should('not.exist');
         cy.get('#author_1').should('not.exist');
     });
 
     it('can be imported from empty list', function() {
         cy.get('#codemetaText').then((elem) =>
             elem.text(JSON.stringify({
                 "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "@type": "SoftwareSourceCode",
                 "name": "My Test Software",
                 "author": [],
             }))
         );
         cy.get('#importCodemeta').click();
 
         cy.get('#author_nb').should('have.value', '0');
         cy.get('#author_0').should('not.exist');
         cy.get('#author_1').should('not.exist');
     });
 });
 
 describe('One full author', function() {
     it('can be exported', function() {
         cy.get('#name').type('My Test Software');
 
         cy.get('#author_nb').should('have.value', '0');
         cy.get('#author_0').should('not.exist');
         cy.get('#author_1').should('not.exist');
         cy.get('#author_1_givenName').should('not.exist');
 
         cy.get('#author_add').click();
         cy.get('#author_nb').should('have.value', '1');
         cy.get('#author_0').should('not.exist');
         cy.get('#author_1').should('exist');
         cy.get('#author_2').should('not.exist');
 
         cy.get('#author_1_givenName').should('have.value', '');
         cy.get('#author_1_familyName').should('have.value', '');
         cy.get('#author_1_email').should('have.value', '');
         cy.get('#author_1_id').should('have.value', '');
         cy.get('#author_1_affiliation').should('have.value', '');
 
         cy.get('#author_1_givenName').type('Jane');
         cy.get('#author_1_familyName').type('Doe');
         cy.get('#author_1_email').type('jdoe@example.org');
         cy.get('#author_1_id').type('http://example.org/~jdoe');
         cy.get('#author_1_affiliation').type('http://example.org/');
 
         cy.get('#generateCodemetaV2').click();
 
         cy.get('#errorMessage').should('have.text', '');
         cy.get('#codemetaText').then((elem) => JSON.parse(elem.text()))
             .should('deep.equal', {
                 "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "type": "SoftwareSourceCode",
                 "name": "My Test Software",
                 "author": [
                     {
                         "type": "Person",
                         "id": "http://example.org/~jdoe",
                         "givenName": "Jane",
                         "familyName": "Doe",
                         "email": "jdoe@example.org",
                         "affiliation": {
                             "type": "Organization",
                             "id": "http://example.org/",
                         }
                     }
                 ],
         });
     });
 
     it('can be imported', function() {
         cy.get('#codemetaText').then((elem) =>
             elem.text(JSON.stringify({
                 "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "@type": "SoftwareSourceCode",
                 "name": "My Test Software",
                 "author": [
                     {
                         "@type": "Person",
                         "@id": "http://example.org/~jdoe",
                         "givenName": "Jane",
                         "familyName": "Doe",
                         "email": "jdoe@example.org",
                         "affiliation": {
                             "@type": "Organization",
                             "@id": "http://example.org/",
                         }
                     }
                 ],
             }))
         );
         cy.get('#importCodemeta').click();
 
         cy.get('#author_nb').should('have.value', '1');
         cy.get('#author_0').should('not.exist');
         cy.get('#author_1').should('exist');
         cy.get('#author_2').should('not.exist');
         cy.get('#author_1_givenName').should('have.value', 'Jane');
         cy.get('#author_1_familyName').should('have.value', 'Doe');
         cy.get('#author_1_email').should('have.value', 'jdoe@example.org');
         cy.get('#author_1_id').should('have.value', 'http://example.org/~jdoe');
         cy.get('#author_1_affiliation').should('have.value', 'http://example.org/');
     });
 });
 
 describe('Affiliation id', function() {
     it('can be exported', function() {
         cy.get('#name').type('My Test Software');
 
         cy.get('#author_add').click();
         cy.get('#author_1_givenName').type('Jane');
         cy.get('#author_1_affiliation').type('http://example.org/');
 
         cy.get('#generateCodemetaV2').click();
 
         cy.get('#errorMessage').should('have.text', '');
         cy.get('#codemetaText').then((elem) => JSON.parse(elem.text()))
             .should('deep.equal', {
                 "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "type": "SoftwareSourceCode",
                 "name": "My Test Software",
                 "author": [
                     {
                         "type": "Person",
                         "givenName": "Jane",
                         "affiliation": {
                             "type": "Organization",
                             "id": "http://example.org/",
                         }
                     }
                 ],
         });
     });
 
     it('can be imported', function() {
         cy.get('#codemetaText').then((elem) =>
             elem.text(JSON.stringify({
                 "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "@type": "SoftwareSourceCode",
                 "name": "My Test Software",
                 "author": [
                     {
                         "@type": "Person",
                         "@id": "http://example.org/~jdoe",
                         "givenName": "Jane",
                         "familyName": "Doe",
                         "email": "jdoe@example.org",
                         "affiliation": {
                             "@type": "Organization",
                             "@id": "http://example.org/",
                         }
                     }
                 ],
             }))
         );
         cy.get('#importCodemeta').click();
 
         cy.get('#author_nb').should('have.value', '1');
         cy.get('#author_0').should('not.exist');
         cy.get('#author_1').should('exist');
         cy.get('#author_2').should('not.exist');
         cy.get('#author_1_affiliation').should('have.value', 'http://example.org/');
     });
 });
 
 describe('Affiliation name', function() {
     it('can be exported', function() {
         cy.get('#name').type('My Test Software');
 
         cy.get('#author_add').click();
         cy.get('#author_1_givenName').type('Jane');
         cy.get('#author_1_affiliation').type('Example Org');
 
         cy.get('#generateCodemetaV2').click();
 
         cy.get('#errorMessage').should('have.text', '');
         cy.get('#codemetaText').then((elem) => JSON.parse(elem.text()))
             .should('deep.equal', {
                 "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "type": "SoftwareSourceCode",
                 "name": "My Test Software",
                 "author": [
                     {
                         "type": "Person",
                         "givenName": "Jane",
                         "affiliation": {
                             "type": "Organization",
                             "name": "Example Org",
                         }
                     }
                 ],
         });
     });
 
     it('can be imported', function() {
         cy.get('#codemetaText').then((elem) =>
             elem.text(JSON.stringify({
                 "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "@type": "SoftwareSourceCode",
                 "name": "My Test Software",
                 "author": [
                     {
                         "@type": "Person",
                         "@id": "http://example.org/~jdoe",
                         "givenName": "Jane",
                         "familyName": "Doe",
                         "email": "jdoe@example.org",
                         "affiliation": {
                             "@type": "Organization",
                             "name": "Example Org",
                         }
                     }
                 ],
             }))
         );
         cy.get('#importCodemeta').click();
 
         cy.get('#author_nb').should('have.value', '1');
         cy.get('#author_0').should('not.exist');
         cy.get('#author_1').should('exist');
         cy.get('#author_2').should('not.exist');
         cy.get('#author_1_affiliation').should('have.value', 'Example Org');
     });
 });
 
 describe('Author order change', function() {
     it('is a noop with a single author', function() {
         cy.get('#name').type('My Test Software');
 
         cy.get('#author_add').click();
         cy.get('#author_1_givenName').type('Jane');
         cy.get('#author_1_affiliation').type('Example Org');
 
         cy.get('#author_1_moveToRight').click();
 
         cy.get('#author_1_givenName').should('have.value', 'Jane');
         cy.get('#author_1_affiliation').should('have.value', 'Example Org');
 
         cy.get('#author_1_moveToLeft').click();
 
         cy.get('#author_1_givenName').should('have.value', 'Jane');
         cy.get('#author_1_affiliation').should('have.value', 'Example Org');
     });
 
     it('flips two authors', function() {
         cy.get('#name').type('My Test Software');
 
         cy.get('#author_add').click();
         cy.get('#author_add').click();
         cy.get('#author_add').click();
         cy.get('#author_1_givenName').type('Jane');
         cy.get('#author_1_affiliation').type('Example Org');
         cy.get('#author_2_givenName').type('John');
         cy.get('#author_2_familyName').type('Doe');
         cy.get('#author_3_givenName').type('Alex');
 
         cy.get('#author_1_moveToRight').click();
 
         cy.get('#author_1_givenName').should('have.value', 'John');
         cy.get('#author_1_familyName').should('have.value', 'Doe');
         cy.get('#author_1_affiliation').should('have.value', '');
 
         cy.get('#author_2_givenName').should('have.value', 'Jane');
         cy.get('#author_2_familyName').should('have.value', '');
         cy.get('#author_2_affiliation').should('have.value', 'Example Org');
 
         cy.get('#author_3_givenName').should('have.value', 'Alex');
         cy.get('#author_3_familyName').should('have.value', '');
         cy.get('#author_3_affiliation').should('have.value', '');
     });
 
     it('updates generated Codemeta', function() {
         cy.get('#name').type('My Test Software');
 
         cy.get('#author_add').click();
         cy.get('#author_add').click();
         cy.get('#author_1_givenName').type('Jane');
         cy.get('#author_1_affiliation').type('Example Org');
         cy.get('#author_2_givenName').type('John');
         cy.get('#author_2_familyName').type('Doe');
 
         cy.get('#generateCodemetaV2').click();
 
         cy.get('#codemetaText').then((elem) => JSON.parse(elem.text()))
             .should('deep.equal', {
                 "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "type": "SoftwareSourceCode",
                 "name": "My Test Software",
                 "author": [
                     {
                         "type": "Person",
                         "givenName": "Jane",
                         "affiliation": {
                             "type": "Organization",
                             "name": "Example Org",
                         }
                     },
                     {
                         "type": "Person",
                         "givenName": "John",
                         "familyName": "Doe",
                     },
                 ],
         });
 
         cy.get('#author_1_moveToRight').click();
 
         cy.get('#codemetaText').then((elem) => JSON.parse(elem.text()))
             .should('deep.equal', {
                 "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "type": "SoftwareSourceCode",
                 "name": "My Test Software",
                 "author": [
                     {
                         "type": "Person",
                         "givenName": "John",
                         "familyName": "Doe",
                     },
                     {
                         "type": "Person",
                         "givenName": "Jane",
                         "affiliation": {
                             "type": "Organization",
                             "name": "Example Org",
                         }
                     },
                 ],
         });
     });
 
     it('wraps around to the right', function() {
         cy.get('#name').type('My Test Software');
 
         cy.get('#author_add').click();
         cy.get('#author_add').click();
         cy.get('#author_add').click();
         cy.get('#author_1_givenName').type('Jane');
         cy.get('#author_1_affiliation').type('Example Org');
         cy.get('#author_2_givenName').type('John');
         cy.get('#author_2_familyName').type('Doe');
         cy.get('#author_3_givenName').type('Alex');
 
         cy.get('#author_1_moveToLeft').click()
 
         cy.get('#author_1_givenName').should('have.value', 'Alex');
         cy.get('#author_1_familyName').should('have.value', '');
         cy.get('#author_1_affiliation').should('have.value', '');
 
         cy.get('#author_2_givenName').should('have.value', 'John');
         cy.get('#author_2_familyName').should('have.value', 'Doe');
         cy.get('#author_2_affiliation').should('have.value', '');
 
         cy.get('#author_3_givenName').should('have.value', 'Jane');
         cy.get('#author_3_familyName').should('have.value', '');
         cy.get('#author_3_affiliation').should('have.value', 'Example Org');
     });
 
     it('wraps around to the left', function() {
         cy.get('#name').type('My Test Software');
 
         cy.get('#author_add').click();
         cy.get('#author_add').click();
         cy.get('#author_add').click();
         cy.get('#author_1_givenName').type('Jane');
         cy.get('#author_1_affiliation').type('Example Org');
         cy.get('#author_2_givenName').type('John');
         cy.get('#author_2_familyName').type('Doe');
         cy.get('#author_3_givenName').type('Alex');
 
         cy.get('#author_3_moveToRight').click()
 
         cy.get('#author_1_givenName').should('have.value', 'Alex');
         cy.get('#author_1_familyName').should('have.value', '');
         cy.get('#author_1_affiliation').should('have.value', '');
 
         cy.get('#author_2_givenName').should('have.value', 'John');
         cy.get('#author_2_familyName').should('have.value', 'Doe');
         cy.get('#author_2_affiliation').should('have.value', '');
 
         cy.get('#author_3_givenName').should('have.value', 'Jane');
         cy.get('#author_3_familyName').should('have.value', '');
         cy.get('#author_3_affiliation').should('have.value', 'Example Org');
     });
 });
 
 describe('One author with a role', function () {
     it('can be exported in both codemeta v2.0 and v3.0 versions', function () {
         cy.get('#name').type('My Test Software');
 
         cy.get('#author_add').click();
         cy.get('#author_1_givenName').type('Jane');
 
         cy.get('#author_1_role_add').click();
         cy.get('#author_1_roleName_0').type('Developer');
         cy.get('#author_1_startDate_0').type('2024-03-04');
         cy.get('#author_1_endDate_0').type('2024-04-03');
 
         cy.get('#generateCodemetaV2').click();
         cy.get('#codemetaText').then((elem) => JSON.parse(elem.text()))
             .should('deep.equal', {
                 "@context": "https://doi.org/10.5063/schema/codemeta-2.0",
                 "type": "SoftwareSourceCode",
                 "name": "My Test Software",
                 "author": [
                     {
                         "type": "Person",
                         "givenName": "Jane"
                     },
                     {
                         "type": "schema:Role",
                         "schema:author": {
                             "type": "Person",
                             "givenName": "Jane"
                         },
                         "schema:roleName": "Developer",
                         "schema:startDate": "2024-03-04",
                         "schema:endDate": "2024-04-03"
                     }
                 ]
             });
 
         cy.get('#generateCodemetaV3').click();
         cy.get('#codemetaText').then((elem) => JSON.parse(elem.text()))
             .should('deep.equal', {
                 "@context": "https://w3id.org/codemeta/3.0",
                 "type": "SoftwareSourceCode",
                 "name": "My Test Software",
                 "author": [
                     {
                         "type": "Person",
                         "givenName": "Jane"
                     },
                     {
                         "type": "Role",
                         "schema:author": {
                             "type": "Person",
                             "givenName": "Jane"
                         },
                         "roleName": "Developer",
                         "startDate": "2024-03-04",
                         "endDate": "2024-04-03"
                     }
                 ]
             });
     });
 
     it('can have two roles', function () {
         cy.get('#name').type('My Test Software');
 
         cy.get('#author_add').click();
         cy.get('#author_1_givenName').type('Jane');
 
         cy.get('#author_1_role_add').click();
         cy.get('#author_1_roleName_0').type('Developer');
         cy.get('#author_1_startDate_0').type('2024-03-04');
         cy.get('#author_1_endDate_0').type('2024-04-03');
 
         cy.get('#author_1_role_add').click();
         cy.get('#author_1_roleName_1').type('Maintainer');
         cy.get('#author_1_startDate_1').type('2024-04-04');
         cy.get('#author_1_endDate_1').type('2024-05-05');
 
         cy.get('#generateCodemetaV3').click();
         cy.get('#codemetaText').then((elem) => JSON.parse(elem.text()))
             .should('deep.equal', {
                 "@context": "https://w3id.org/codemeta/3.0",
                 "type": "SoftwareSourceCode",
                 "name": "My Test Software",
                 "author": [
                     {
                         "type": "Person",
                         "givenName": "Jane"
                     },
                     {
                         "type": "Role",
                         "schema:author": {
                             "type": "Person",
                             "givenName": "Jane"
                         },
                         "roleName": "Maintainer",
                         "startDate": "2024-04-04",
                         "endDate": "2024-05-05"
                     },
                     {
                         "type": "Role",
                         "schema:author": {
                             "type": "Person",
                             "givenName": "Jane"
                         },
                         "roleName": "Developer",
                         "startDate": "2024-03-04",
                         "endDate": "2024-04-03"
                     }
                 ]
             });
     });
 
     it('can be deleted then added again', function () {
         cy.get('#name').type('My Test Software');
 
         cy.get('#author_add').click();
         cy.get('#author_1_givenName').type('Jane');
 
         cy.get('#author_1_role_add').click();
         cy.get('#author_1_roleName_0').type('Developer');
         cy.get('#author_1_startDate_0').type('2024-03-04');
         cy.get('#author_1_endDate_0').type('2024-04-03');
         cy.get('#author_1_role_remove_0').click();
 
         cy.get('#author_1_role_add').click();
         cy.get('#author_1_roleName_1').type('Maintainer');
         cy.get('#author_1_startDate_1').type('2024-04-04');
         cy.get('#author_1_endDate_1').type('2024-05-05');
 
         cy.get('#generateCodemetaV3').click();
         cy.get('#codemetaText').then((elem) => JSON.parse(elem.text()))
             .should('deep.equal', {
                 "@context": "https://w3id.org/codemeta/3.0",
                 "type": "SoftwareSourceCode",
                 "name": "My Test Software",
                 "author": [
                     {
                         "type": "Person",
                         "givenName": "Jane"
                     },
                     {
                         "type": "Role",
                         "schema:author": {
                             "type": "Person",
                             "givenName": "Jane"
                         },
                         "roleName": "Maintainer",
                         "startDate": "2024-04-04",
                         "endDate": "2024-05-05"
                     }
                 ]
             });
     });
 
     it('can be imported', function () {
-        // TODO
+        cy.get('#codemetaText').then((elem) =>
+            elem.text(JSON.stringify({
+                "@context": "https://w3id.org/codemeta/3.0",
+                "type": "SoftwareSourceCode",
+                "name": "My Test Software",
+                "author": [
+                    {
+                        "type": "Role",
+                        "schema:author": {
+                            "type": "Person",
+                            "givenName": "Jane"
+                        },
+                        "roleName": "Developer",
+                        "startDate": "2024-03-04",
+                        "endDate": "2024-04-03"
+                    }
+                ]
+            }))
+        );
+        cy.get('#importCodemeta').click();
+
+        cy.get('#author_1_givenName').should('have.value', 'Jane');
+        cy.get('#author_1_roleName_0').should('have.value', 'Developer');
+        cy.get('#author_1_startDate_0').should('have.value', '2024-03-04');
+        cy.get('#author_1_endDate_0').should('have.value', '2024-04-03');
+    });
+
+    it('and second one for the same author can be imported (and they are merged)', function () {
+        cy.get('#codemetaText').then((elem) =>
+            elem.text(JSON.stringify({
+                "@context": "https://w3id.org/codemeta/3.0",
+                "type": "SoftwareSourceCode",
+                "name": "My Test Software",
+                "author": [
+                    {
+                        "type": "Role",
+                        "schema:author": {
+                            "type": "Person",
+                            "givenName": "Jane"
+                        },
+                        "roleName": "Maintainer",
+                        "startDate": "2024-04-04",
+                        "endDate": "2024-05-05"
+                    },
+                    {
+                        "type": "Role",
+                        "schema:author": {
+                            "type": "Person",
+                            "givenName": "Jane"
+                        },
+                        "roleName": "Developer",
+                        "startDate": "2024-03-04",
+                        "endDate": "2024-04-03"
+                    }
+                ]
+            }))
+        );
+        cy.get('#importCodemeta').click();
+
+        cy.get('#author_nb').should('have.value', '1');
+        cy.get('#author_1_givenName').should('have.value', 'Jane');
+        cy.get('#author_1_roleName_0').should('have.value', 'Maintainer');
+        cy.get('#author_1_startDate_0').should('have.value', '2024-04-04');
+        cy.get('#author_1_endDate_0').should('have.value', '2024-05-05');
+        cy.get('#author_1_roleName_1').should('have.value', 'Developer');
+        cy.get('#author_1_startDate_1').should('have.value', '2024-03-04');
+        cy.get('#author_1_endDate_1').should('have.value', '2024-04-03');
     });
 });
 
 describe('Multiple authors', function () {
     it('who both have roles can be exported', function () {
         cy.get('#name').type('My Test Software');
 
         cy.get('#author_add').click();
         cy.get('#author_1_givenName').type('Jane');
 
         cy.get('#author_add').click();
         cy.get('#author_2_givenName').type('Joe');
 
         cy.get('#author_1_role_add').click();
         cy.get('#author_1_roleName_0').type('Developer');
         cy.get('#author_1_startDate_0').type('2024-03-04');
         cy.get('#author_1_endDate_0').type('2024-04-03');
 
         cy.get('#author_2_role_add').click();
         cy.get('#author_2_roleName_0').type('Maintainer');
         cy.get('#author_2_startDate_0').type('2024-04-04');
         cy.get('#author_2_endDate_0').type('2024-05-05');
 
         cy.get('#generateCodemetaV3').click();
         cy.get('#codemetaText').then((elem) => JSON.parse(elem.text()))
             .should('deep.equal', {
                 "@context": "https://w3id.org/codemeta/3.0",
                 "type": "SoftwareSourceCode",
                 "name": "My Test Software",
                 "author": [
                     {
                         "type": "Person",
                         "givenName": "Jane"
                     },
                     {
                         "type": "Role",
                         "schema:author": {
                             "type": "Person",
                             "givenName": "Jane"
                         },
                         "roleName": "Developer",
                         "startDate": "2024-03-04",
                         "endDate": "2024-04-03"
                     },
                     {
                         "type": "Person",
                         "givenName": "Joe"
                     },
                     {
                         "type": "Role",
                         "schema:author": {
                             "type": "Person",
                             "givenName": "Joe"
                         },
                         "roleName": "Maintainer",
                         "startDate": "2024-04-04",
                         "endDate": "2024-05-05"
                     }
                 ]
             });
     });
 
     it('whose one has a role and the other not can be exported', function () {
         cy.get('#name').type('My Test Software');
 
         cy.get('#author_add').click();
         cy.get('#author_1_givenName').type('Jane');
 
         cy.get('#author_add').click();
         cy.get('#author_2_givenName').type('Joe');
 
         cy.get('#author_1_role_add').click();
         cy.get('#author_1_roleName_0').type('Developer');
         cy.get('#author_1_startDate_0').type('2024-03-04');
         cy.get('#author_1_endDate_0').type('2024-04-03');
 
         cy.get('#generateCodemetaV3').click();
         cy.get('#codemetaText').then((elem) => JSON.parse(elem.text()))
             .should('deep.equal', {
                 "@context": "https://w3id.org/codemeta/3.0",
                 "type": "SoftwareSourceCode",
                 "name": "My Test Software",
                 "author": [
                     {
                         "type": "Person",
                         "givenName": "Jane"
                     },
                     {
                         "type": "Role",
                         "schema:author": {
                             "type": "Person",
                             "givenName": "Jane"
                         },
                         "roleName": "Developer",
                         "startDate": "2024-03-04",
                         "endDate": "2024-04-03"
                     },
                     {
                         "type": "Person",
                         "givenName": "Joe"
                     }
                 ]
             });
     });
+
+    it('who both have roles can be imported', function () {
+        cy.get('#codemetaText').then((elem) =>
+            elem.text(JSON.stringify({
+                 "@context": "https://w3id.org/codemeta/3.0",
+                "type": "SoftwareSourceCode",
+                "name": "My Test Software",
+                "author": [
+                    {
+                        "type": "Role",
+                        "schema:author": {
+                            "type": "Person",
+                            "givenName": "Jane"
+                        },
+                        "roleName": "Developer",
+                        "startDate": "2024-03-04",
+                        "endDate": "2024-04-03"
+                    },
+                    {
+                        "type": "Role",
+                        "schema:author": {
+                            "type": "Person",
+                            "givenName": "Joe"
+                        },
+                        "roleName": "Maintainer",
+                        "startDate": "2024-04-04",
+                        "endDate": "2024-05-05"
+                    }
+                ]
+            }))
+        );
+        cy.get('#importCodemeta').click();
+
+        cy.get('#author_nb').should('have.value', '2');
+        cy.get('#author_1_givenName').should('have.value', 'Jane');
+        cy.get('#author_1_roleName_0').should('have.value', 'Developer');
+        cy.get('#author_1_startDate_0').should('have.value', '2024-03-04');
+        cy.get('#author_1_endDate_0').should('have.value', '2024-04-03');
+        cy.get('#author_2_givenName').should('have.value', 'Joe');
+        cy.get('#author_2_roleName_0').should('have.value', 'Maintainer');
+        cy.get('#author_2_startDate_0').should('have.value', '2024-04-04');
+        cy.get('#author_2_endDate_0').should('have.value', '2024-05-05');
+    });
+
+    it('whose one has a role and the other not can be imported', function () {
+        cy.get('#codemetaText').then((elem) =>
+            elem.text(JSON.stringify({
+                 "@context": "https://w3id.org/codemeta/3.0",
+                "type": "SoftwareSourceCode",
+                "name": "My Test Software",
+                "author": [
+                    {
+                        "type": "Role",
+                        "schema:author": {
+                            "type": "Person",
+                            "givenName": "Jane"
+                        },
+                        "roleName": "Developer",
+                        "startDate": "2024-03-04",
+                        "endDate": "2024-04-03"
+                    },
+                    {
+                        "type": "Person",
+                        "givenName": "Joe"
+                    }
+                ]
+            }))
+        );
+        cy.get('#importCodemeta').click();
+
+        cy.get('#author_nb').should('have.value', '2');
+        cy.get('#author_1_givenName').should('have.value', 'Joe');
+        cy.get('#author_2_givenName').should('have.value', 'Jane');
+        cy.get('#author_2_roleName_0').should('have.value', 'Developer');
+        cy.get('#author_2_startDate_0').should('have.value', '2024-03-04');
+        cy.get('#author_2_endDate_0').should('have.value', '2024-04-03');
+    });
 });
diff --git a/js/codemeta_generation.js b/js/codemeta_generation.js
index 8a603c7..92d09ae 100644
--- a/js/codemeta_generation.js
+++ b/js/codemeta_generation.js
@@ -1,377 +1,429 @@
 /**
  * Copyright (C) 2019-2020  The Software Heritage developers
  * See the AUTHORS file at the top-level directory of this distribution
  * License: GNU Affero General Public License version 3, or any later version
  * See top-level LICENSE file for more information
  */
 
 "use strict";
 
 const LOCAL_CONTEXT_PATH = "./data/contexts/codemeta-local.jsonld";
 const LOCAL_CONTEXT_URL = "local";
 const CODEMETA_CONTEXTS = {
     "2.0": {
         path: "./data/contexts/codemeta-2.0.jsonld",
         url: "https://doi.org/10.5063/schema/codemeta-2.0"
     },
     "3.0": {
         path: "./data/contexts/codemeta-3.0.jsonld",
         url: "https://w3id.org/codemeta/3.0"
     }
 }
 
 const SPDX_PREFIX = 'https://spdx.org/licenses/';
 
 const loadContextData = async () => {
     const [contextLocal, contextV2, contextV3] =
         await Promise.all([
             fetch(LOCAL_CONTEXT_PATH).then(response => response.json()),
             fetch(CODEMETA_CONTEXTS["2.0"].path).then(response => response.json()),
             fetch(CODEMETA_CONTEXTS["3.0"].path).then(response => response.json())
         ]);
     return {
         [LOCAL_CONTEXT_URL]: contextLocal,
         [CODEMETA_CONTEXTS["2.0"].url]: contextV2,
         [CODEMETA_CONTEXTS["3.0"].url]: contextV3
     }
 }
 
 const getJsonldCustomLoader = contexts => {
     return url => {
         const xhrDocumentLoader = jsonld.documentLoaders.xhr();
         if (url in contexts) {
             return {
                 contextUrl: null,
                 document: contexts[url],
                 documentUrl: url
             };
         }
         return xhrDocumentLoader(url);
     }
 };
 
 const initJsonldLoader = contexts => {
     jsonld.documentLoader = getJsonldCustomLoader(contexts);
 };
 
 function emptyToUndefined(v) {
     if (v == null || v == "")
         return undefined;
     else
         return v;
 }
 
 function getIfSet(query) {
     return emptyToUndefined(document.querySelector(query).value);
 }
 
 function setIfDefined(query, value) {
     if (value !== undefined) {
         document.querySelector(query).value = value;
     }
 }
 
 function getLicenses() {
     let selectedLicenses = Array.from(document.getElementById("selected-licenses").children);
     return selectedLicenses.map(licenseDiv => SPDX_PREFIX + licenseDiv.children[0].innerText);
 }
 
 // Names of codemeta properties with a matching HTML field name
 const directCodemetaFields = [
     'codeRepository',
     'contIntegration',
     'dateCreated',
     'datePublished',
     'dateModified',
     'downloadUrl',
     'issueTracker',
     'name',
     'version',
     'identifier',
     'description',
     'applicationCategory',
     'releaseNotes',
     'funding',
     'developmentStatus',
     'isSourceCodeOf',
     'isPartOf',
     'referencePublication'
 ];
 
 const splittedCodemetaFields = [
     ['keywords', ','],
     ['programmingLanguage', ','],
     ['runtimePlatform', ','],
     ['operatingSystem', ','],
     ['softwareRequirements', '\n'],
     ['relatedLink', '\n'],
 ]
 
 // Names of codemeta properties with a matching HTML field name,
 // in a Person object
 const directPersonCodemetaFields = [
     'givenName',
     'familyName',
     'email',
     'affiliation',
 ];
 
 const directRoleCodemetaFields = [
     'roleName',
     'startDate',
     'endDate',
 ];
 
 const directReviewCodemetaFields = [
     'reviewAspect',
     'reviewBody'
 ];
 
-const crossedCodemetaFields = {
+const crossCodemetaFields = {
     "contIntegration": ["contIntegration", "continuousIntegration"],
-    "embargoDate": ["embargoDate", "embargoEndDate"],
+    // "embargoDate": ["embargoDate", "embargoEndDate"], Not present in the form yet TODO ?
 };
 
 function generateShortOrg(fieldName) {
     var affiliation = getIfSet(fieldName);
     if (affiliation !== undefined) {
         if (isUrl(affiliation)) {
             return {
                 "@type": "Organization",
                 "@id": affiliation,
             };
         }
         else {
             return {
                 "@type": "Organization",
                 "name": affiliation,
             };
         }
     }
     else {
         return undefined;
     }
 }
 
 function generatePerson(idPrefix) {
     var doc = {
         "@type": "Person",
     }
     var id = getIfSet(`#${idPrefix}_id`);
     if (id !== undefined) {
         doc["@id"] = id;
     }
     directPersonCodemetaFields.forEach(function (item, index) {
         doc[item] = getIfSet(`#${idPrefix}_${item}`);
     });
     doc["affiliation"] = generateShortOrg(`#${idPrefix}_affiliation`);
 
     return doc;
 }
 
 function generateRole(id) {
     const doc = {
         "@type": "Role"
     };
     directRoleCodemetaFields.forEach(function (item, index) {
         doc[item] = getIfSet(`#${id} .${item}`);
     });
     return doc;
 }
 
 function generateRoles(idPrefix, person) {
     const roles = [];
     const roleNodes = document.querySelectorAll(`ul[id^=${idPrefix}_role_`);
     roleNodes.forEach(roleNode => {
         const role = generateRole(roleNode.id);
         role["schema:author"] = person; // Prefix with "schema:" to prevent it from expanding into a list
         roles.push(role);
     });
     return roles;
 }
 
 function generatePersons(prefix) {
     var persons = [];
     var nbPersons = getNbPersons(prefix);
 
     for (let personId = 1; personId <= nbPersons; personId++) {
         const idPrefix = `${prefix}_${personId}`;
         const person = generatePerson(idPrefix);
         persons.push(person);
         const roles = generateRoles(idPrefix, person);
         if (roles.length > 0) {
             persons = persons.concat(roles);
         }
     }
 
     return persons;
 }
 
 function generateReview() {
     const doc = {
         "@type": "Review"
     };
     directReviewCodemetaFields.forEach(function (item, index) {
         doc[item] = getIfSet(`#${item}`);
     });
     return doc;
 }
 
 async function buildExpandedJson() {
     var doc = {
         "@context": LOCAL_CONTEXT_URL,
         "@type": "SoftwareSourceCode",
     };
 
     let licenses = getLicenses();
     if (licenses.length > 0) {
         doc["license"] = licenses;
     }
 
     // Generate most fields
     directCodemetaFields.forEach(function (item, index) {
         doc[item] = getIfSet('#' + item)
     });
 
     doc["funder"] = generateShortOrg('#funder', doc["affiliation"]);
 
     const review = generateReview();
     if (review["reviewAspect"] || review["reviewBody"]) {
         doc["review"] = generateReview();
     }
 
     // Generate simple fields parsed simply by splitting
     splittedCodemetaFields.forEach(function (item, index) {
         const id = item[0];
         const separator = item[1];
         const value = getIfSet('#' + id);
         if (value !== undefined) {
             doc[id] = value.split(separator).map(trimSpaces);
         }
     });
 
     // Generate dynamic fields
     var authors = generatePersons('author');
     if (authors.length > 0) {
         doc["author"] = authors;
     }
     var contributors = generatePersons('contributor');
     if (contributors.length > 0) {
         doc["contributor"] = contributors;
     }
 
-    for (const [key, values] of Object.entries(crossedCodemetaFields)) {
-        values.forEach(value => {
-           doc[value] = doc[key];
+    for (const [key, items] of Object.entries(crossCodemetaFields)) {
+        items.forEach(item => {
+           doc[item] = doc[key];
         });
     }
     return await jsonld.expand(doc);
 }
 
 // v2.0 is still default version for generation, for now
 async function generateCodemeta(codemetaVersion = "2.0") {
     var inputForm = document.querySelector('#inputForm');
     var codemetaText, errorHTML;
 
     if (inputForm.checkValidity()) {
         const expanded = await buildExpandedJson();
         const compacted = await jsonld.compact(expanded, CODEMETA_CONTEXTS[codemetaVersion].url);
         codemetaText = JSON.stringify(compacted, null, 4);
         errorHTML = "";
     }
     else {
         codemetaText = "";
         errorHTML = "invalid input (see error above)";
         inputForm.reportValidity();
     }
 
     document.querySelector('#codemetaText').innerText = codemetaText;
     setError(errorHTML);
 
 
     // Run validator on the exported value, for extra validation.
     // If this finds a validation, it means there is a bug in our code (either
     // generation or validation), and the generation MUST NOT generate an
     // invalid codemeta file, regardless of user input.
     if (codemetaText && !validateDocument(JSON.parse(codemetaText))) {
         alert('Bug detected! The data you wrote is correct; but for some reason, it seems we generated an invalid codemeta.json. Please report this bug at https://github.com/codemeta/codemeta-generator/issues/new and copy-paste the generated codemeta.json file. Thanks!');
     }
 
     if (codemetaText) {
         // For restoring the form state on page reload
         sessionStorage.setItem('codemetaText', codemetaText);
     }
 }
 
 // Imports a single field (name or @id) from an Organization.
 function importShortOrg(fieldName, doc) {
     if (doc !== undefined) {
         // Use @id if set, else use name
         setIfDefined(fieldName, doc["name"]);
         setIfDefined(fieldName, getDocumentId(doc));
     }
 }
 
+function importReview(doc) {
+    if (doc !== undefined) {
+        directReviewCodemetaFields.forEach(item => {
+            setIfDefined('#' + item, doc[item]);
+        });
+    }
+}
+
+function authorsEqual(author1, author2) {
+    // TODO should test more properties for equality?
+    return author1.givenName === author2.givenName
+        && author1.email === author2.email;
+}
+
+function getSingleAuthorsFromRoles(docs) {
+    return docs.filter(doc => getDocumentType(doc) === "Role")
+        .map(doc => doc["schema:author"])
+        .reduce((authorSet, currentAuthor) => {
+            const foundAuthor = authorSet.find(author => authorsEqual(author, currentAuthor));
+            if (!foundAuthor) {
+                return authorSet.concat([currentAuthor]);
+            } else {
+                return authorSet;
+            }
+        }, []);
+}
+
+function importRoles(personPrefix, roles) {
+    roles.forEach(role => {
+        const roleId = addRole(`${personPrefix}`);
+        directRoleCodemetaFields.forEach(item => {
+            setIfDefined(`#${personPrefix}_${item}_${roleId}`, role[item]);
+        });
+    });
+}
+
 function importPersons(prefix, legend, docs) {
     if (docs === undefined) {
         return;
     }
 
-    docs.forEach(function (doc, index) {
+    let allAuthorDocs = docs.filter(doc => getDocumentType(doc) === "Person");
+    allAuthorDocs = allAuthorDocs.concat(getSingleAuthorsFromRoles(docs));
+
+    allAuthorDocs.forEach(function (doc, index) {
         var personId = addPerson(prefix, legend);
 
         setIfDefined(`#${prefix}_${personId}_id`, getDocumentId(doc));
         directPersonCodemetaFields.forEach(function (item, index) {
             setIfDefined(`#${prefix}_${personId}_${item}`, doc[item]);
         });
 
-        importShortOrg(`#${prefix}_${personId}_affiliation`, doc['affiliation'])
-    })
+        importShortOrg(`#${prefix}_${personId}_affiliation`, doc['affiliation']);
+
+        const roles = docs.filter(currentDoc => getDocumentType(currentDoc) === "Role")
+            .filter(currentDoc => authorsEqual(currentDoc["schema:author"], doc));
+        importRoles(`${prefix}_${personId}`, roles);
+    });
 }
 
 async function importCodemeta() {
     var inputForm = document.querySelector('#inputForm');
     var doc = await parseAndValidateCodemeta(false);
     resetForm();
 
     if (doc['license'] !== undefined) {
         if (typeof doc['license'] === 'string') {
             doc['license'] = [doc['license']];
         }
 
         doc['license'].forEach(l => {
             if (l.indexOf(SPDX_PREFIX) !== 0) { return; }
             let licenseId = l.substring(SPDX_PREFIX.length);
             insertLicenseElement(licenseId);
         });
     }
 
     directCodemetaFields.forEach(function (item, index) {
         setIfDefined('#' + item, doc[item]);
     });
     importShortOrg('#funder', doc["funder"]);
+    importReview(doc["review"]);
 
     // Import simple fields by joining on their separator
     splittedCodemetaFields.forEach(function (item, index) {
         const id = item[0];
         const separator = item[1];
         let value = doc[id];
         if (value !== undefined) {
             if (Array.isArray(value)) {
                 value = value.join(separator);
             }
             setIfDefined('#' + id, value);
         }
     });
 
+    for (const [key, items] of Object.entries(crossCodemetaFields)) {
+        let value = "";
+        items.forEach(item => {
+           value = doc[item] || value;
+        });
+        setIfDefined(`#${key}`, value);
+    }
+
     importPersons('author', 'Author', doc['author'])
     importPersons('contributor', 'Contributor', doc['contributor'])
 }
 
 function loadStateFromStorage() {
     var codemetaText = sessionStorage.getItem('codemetaText')
     if (codemetaText) {
         document.querySelector('#codemetaText').innerText = codemetaText;
         importCodemeta();
     }
 }
diff --git a/js/dynamic_form.js b/js/dynamic_form.js
index 7f43bd2..f15aba8 100644
--- a/js/dynamic_form.js
+++ b/js/dynamic_form.js
@@ -1,230 +1,232 @@
 /**
  * Copyright (C) 2019  The Software Heritage developers
  * See the AUTHORS file at the top-level directory of this distribution
  * License: GNU Affero General Public License version 3, or any later version
  * See top-level LICENSE file for more information
  */
 
 "use strict";
 
 // List of all HTML fields in a Person fieldset.
 const personFields = [
     'givenName',
     'familyName',
     'email',
     'id',
     'affiliation',
 ];
 
 function createPersonFieldset(personPrefix, legend) {
     // Creates a fieldset containing inputs for informations about a person
     var fieldset = document.createElement("fieldset")
     var moveButtons;
     fieldset.classList.add("person");
     fieldset.classList.add("leafFieldset");
     fieldset.id = personPrefix;
 
     fieldset.innerHTML = `
         <legend>${legend}</legend>
         <div class="moveButtons">
             <input type="button" id="${personPrefix}_moveToLeft" value="<" class="moveToLeft"
                 title="Moves this person to the left." />
             <input type="button" id="${personPrefix}_moveToRight" value=">" class="moveToRight"
                 title="Moves this person to the right." />
         </div>
         <p>
             <label for="${personPrefix}_givenName">Given name</label>
             <input type="text" id="${personPrefix}_givenName" name="${personPrefix}_givenName"
                 placeholder="Jane" required="true" />
         </p>
         <p>
             <label for="${personPrefix}_familyName">Family name</label>
             <input type="text" id="${personPrefix}_familyName" name="${personPrefix}_familyName"
                 placeholder="Doe" />
         </p>
         <p>
             <label for="${personPrefix}_email">E-mail address</label>
             <input type="email" id="${personPrefix}_email" name="${personPrefix}_email"
                 placeholder="jane.doe@example.org" />
         </p>
         <p>
             <label for="${personPrefix}_id">URI</label>
             <input type="url" id="${personPrefix}_id" name="${personPrefix}_id"
                 placeholder="http://orcid.org/0000-0002-1825-0097" />
         </p>
         <p>
         <label for="${personPrefix}_affiliation">Affiliation</label>
             <input type="text" id="${personPrefix}_affiliation" name="${personPrefix}_affiliation"
                 placeholder="Department of Computer Science, University of Pisa" />
         </p>
         <input type="hidden" id="${personPrefix}_role_index" value="0" />
         <input type="button" id="${personPrefix}_role_add" value="Add one role" />
     `;
 
     return fieldset;
 }
 
 function addPersonWithId(container, prefix, legend, id) {
     var personPrefix = `${prefix}_${id}`;
     var fieldset = createPersonFieldset(personPrefix, `${legend} #${id}`);
 
     container.appendChild(fieldset);
 
     document.querySelector(`#${personPrefix}_moveToLeft`)
         .addEventListener('click', () => movePerson(prefix, id, "left"));
     document.querySelector(`#${personPrefix}_moveToRight`)
         .addEventListener('click', () => movePerson(prefix, id, "right"));
     document.querySelector(`#${personPrefix}_role_add`)
         .addEventListener('click', () => addRole(personPrefix));
 }
 
 function movePerson(prefix, id1, direction) {
     var nbPersons = getNbPersons(prefix);
     var id2;
 
     // Computer id2, the id of the person to flip id1 with (wraps around the
     // end of the list of persons)
     if (direction == "left") {
         id2 = id1 - 1;
         if (id2 <= 0) {
             id2 = nbPersons;
         }
     }
     else {
         id2 = id1 + 1;
         if (id2 > nbPersons) {
             id2 = 1;
         }
     }
 
     // Flip the field values, one by one
     personFields.forEach((fieldName) => {
         var field1 = document.querySelector(`#${prefix}_${id1}_${fieldName}`);
         var field2 = document.querySelector(`#${prefix}_${id2}_${fieldName}`);
         var value1 = field1.value;
         var value2 = field2.value;
         field2.value = value1;
         field1.value = value2;
     });
 
     // Form was changed; regenerate
     generateCodemeta();
 }
 
 function addPerson(prefix, legend) {
     var container = document.querySelector(`#${prefix}_container`);
     var personId = getNbPersons(prefix) + 1;
 
     addPersonWithId(container, prefix, legend, personId);
 
     setNbPersons(prefix, personId);
 
     return personId;
 }
 
 function removePerson(prefix) {
     var personId = getNbPersons(prefix);
 
     document.querySelector(`#${prefix}_${personId}`).remove();
 
     setNbPersons(prefix, personId - 1);
 }
 
 // Initialize a group of persons (authors, contributors) on page load.
 // Useful if the page is reloaded.
 function initPersons(prefix, legend) {
     var nbPersons = getNbPersons(prefix);
     var personContainer = document.querySelector(`#${prefix}_container`)
 
     for (let personId = 1; personId <= nbPersons; personId++) {
         addPersonWithId(personContainer, prefix, legend, personId);
     }
 }
 
 function removePersons(prefix) {
     var nbPersons = getNbPersons(prefix);
     var personContainer = document.querySelector(`#${prefix}_container`)
 
     for (let personId = 1; personId <= nbPersons; personId++) {
         removePerson(prefix)
     }
 }
 
 function addRole(personPrefix) {
     const roleButtonGroup = document.querySelector(`#${personPrefix}_role_add`);
     const roleIndexNode = document.querySelector(`#${personPrefix}_role_index`);
     const roleIndex = parseInt(roleIndexNode.value, 10);
 
     const ul = document.createElement("ul")
     ul.classList.add("role");
     ul.id = `${personPrefix}_role_${roleIndex}`;
 
     ul.innerHTML = `
         <li><label for="${personPrefix}_roleName_${roleIndex}">Role</label>
             <input type="text" class="roleName" id="${personPrefix}_roleName_${roleIndex}" name="${personPrefix}_roleName_${roleIndex}"
                 placeholder="Developer" size="10" /></li>
         <li><label for="${personPrefix}_startDate_${roleIndex}">Start date:</label>
             <input type="date" class="startDate" id="${personPrefix}_startDate_${roleIndex}" name="${personPrefix}_startDate_${roleIndex}" /></li>
         <li><label for="${personPrefix}_endDate_${roleIndex}">End date:</label>
             <input type="date" class="endDate" id="${personPrefix}_endDate_${roleIndex}" name="${personPrefix}_endDate_${roleIndex}" /></li>
         <li><input type="button" id="${personPrefix}_role_remove_${roleIndex}" value="X" title="Remove role" /></li>
     `;
     roleButtonGroup.after(ul);
 
     document.querySelector(`#${personPrefix}_role_remove_${roleIndex}`)
         .addEventListener('click', () => removeRole(personPrefix, roleIndex));
 
     roleIndexNode.value = roleIndex + 1;
+
+    return roleIndex;
 }
 
 function removeRole(personPrefix, roleIndex) {
     document.querySelector(`#${personPrefix}_role_${roleIndex}`).remove();
 }
 
 function resetForm() {
     removePersons('author');
     removePersons('contributor');
     // Reset the list of selected licenses
     document.getElementById("selected-licenses").innerHTML = '';
 
     // Reset the form after deleting elements, so nbPersons doesn't get
     // reset before it's read.
     document.querySelector('#inputForm').reset();
 }
 
 function fieldToLower(event) {
     event.target.value = event.target.value.toLowerCase();
 }
 
 function initCallbacks() {
     document.querySelector('#license')
         .addEventListener('change', validateLicense);
 
     document.querySelector('#generateCodemetaV2').disabled = false;
     document.querySelector('#generateCodemetaV2')
         .addEventListener('click', () => generateCodemeta("2.0"));
 
     document.querySelector('#generateCodemetaV3').disabled = false;
     document.querySelector('#generateCodemetaV3')
         .addEventListener('click', () => generateCodemeta("3.0"));
 
     document.querySelector('#resetForm')
         .addEventListener('click', resetForm);
 
     document.querySelector('#validateCodemeta').disabled = false;
     document.querySelector('#validateCodemeta')
         .addEventListener('click', () => parseAndValidateCodemeta(true));
 
     document.querySelector('#importCodemeta').disabled = false;
     document.querySelector('#importCodemeta')
         .addEventListener('click', importCodemeta);
 
     document.querySelector('#inputForm')
         .addEventListener('change', () => generateCodemeta());
 
     document.querySelector('#developmentStatus')
         .addEventListener('change', fieldToLower);
 
     initPersons('author', 'Author');
     initPersons('contributor', 'Contributor');
 }
diff --git a/js/validation/index.js b/js/validation/index.js
index c2ca222..2f7d01f 100644
--- a/js/validation/index.js
+++ b/js/validation/index.js
@@ -1,94 +1,99 @@
 /**
  * Copyright (C) 2020  The Software Heritage developers
  * See the AUTHORS file at the top-level directory of this distribution
  * License: GNU Affero General Public License version 3, or any later version
  * See top-level LICENSE file for more information
  */
 
 /*
  * Reads a Codemeta file and shows human-friendly errors on it.
  *
  * This validator intentionaly does not use a schema, in order to show errors
  * that are easy to understand for users with no understanding of JSON-LD.
  */
 
 
 function validateDocument(doc) {
     if (!Array.isArray(doc) && typeof doc != 'object') {
         setError("Document must be an object (starting and ending with { and }), not ${typeof doc}.")
         return false;
     }
     // TODO: validate id/@id
 
+    if (doc["@context"] === undefined) {
+        setError("Missing context (required to determine import version).")
+        return false;
+    }
+
     // TODO: check there is either type or @type but not both
     var type = getDocumentType(doc);
     if (type === undefined) {
         setError("Missing type (must be SoftwareSourceCode or SoftwareApplication).")
         return false;
     }
     else if (!isCompactTypeEqual(type, "SoftwareSourceCode") && !isCompactTypeEqual(type, "SoftwareApplication")) {
         // Check this before other fields, as a wrong type error is more
         // understandable than "invalid field".
         setError(`Wrong document type: must be "SoftwareSourceCode"/"SoftwareApplication", not ${JSON.stringify(type)}`)
         return false;
     }
     else {
         return Object.entries(doc).every((entry) => {
             var fieldName = entry[0];
             var subdoc = entry[1];
             if (fieldName == "@context") {
                 // Was checked before
                 return true;
             }
             else if (fieldName == "type" || fieldName == "@type") {
                 // Was checked before
                 return true;
             }
             else if (isFieldFromOtherVersionToIgnore(fieldName)) {
                 // Do not check fields from other versions FIXME
                 return true;
             }
             else {
                 var validator = softwareFieldValidators[fieldName];
                 if (validator === undefined) {
                     // TODO: find if it's a field that belongs to another type,
                     // and suggest that to the user
                     setError(`Unknown field "${fieldName}".`)
                     return false;
                 }
                 else {
                     return validator(fieldName, subdoc);
                 }
             }
         });
     }
 }
 
 
 async function parseAndValidateCodemeta(showPopup) {
     var codemetaText = document.querySelector('#codemetaText').innerText;
     let parsed, doc;
 
     try {
         parsed = JSON.parse(codemetaText);
     }
     catch (e) {
         setError(`Could not read codemeta document because it is not valid JSON (${e}). Check for missing or extra quote, colon, or bracket characters.`);
         return;
     }
 
     setError("");
 
     var isValid = validateDocument(parsed);
     if (showPopup) {
         if (isValid) {
             alert('Document is valid!')
         }
         else {
             alert('Document is invalid.');
         }
     }
 
-    doc = await jsonld.compact(parsed, CODEMETA_CONTEXTS["2.0"].url); // Only import codemeta v2.0 for now
+    doc = await jsonld.compact(parsed, parsed["@context"]);
     return doc;
 }