diff --git a/data/rcsb-graphql/codegen.js b/data/rcsb-graphql/codegen.js
new file mode 100644
index 0000000000000000000000000000000000000000..e66716d8ecab5d1b66e8e993d91d91465e950e8b
--- /dev/null
+++ b/data/rcsb-graphql/codegen.js
@@ -0,0 +1,18 @@
+const { generate } = require('graphql-code-generator')
+const path = require('path')
+
+const basePath = path.join(__dirname, '..', '..', 'src', 'servers', 'model', 'properties', 'rcsb', 'graphql')
+
+generate({
+    args: [
+        path.join(basePath, 'symmetry.gql.ts')
+    ],
+    schema: 'http://rest-experimental.rcsb.org/graphql',
+    template: 'typescript',
+    out: path.join(basePath),
+    skipSchema: true,
+    overwrite: true,
+    config: path.join(__dirname, 'codegen.json')
+}, true).then(
+    console.log('done')
+).catch(e => console.error(e))
\ No newline at end of file
diff --git a/data/rcsb-graphql/codegen.json b/data/rcsb-graphql/codegen.json
new file mode 100644
index 0000000000000000000000000000000000000000..a005e9369d5807dfdd581297c9556aa8a08ccbba
--- /dev/null
+++ b/data/rcsb-graphql/codegen.json
@@ -0,0 +1,8 @@
+{
+    "flattenTypes": false,
+    "generatorConfig": {
+        "printTime": true,
+        "immutableTypes": true,
+        "resolvers": false
+    }
+}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index d150b46ca731750dc9d2ed16995cbadd4e52fed2..7c984dd0b938e852ed5cd265f4bf59b79e191c9b 100644
Binary files a/package-lock.json and b/package-lock.json differ
diff --git a/package.json b/package.json
index dead84e32b58210e47db72fb3e84d85d2db0a32c..0627484b076a23d20d22ae187269ec66c30f35a9 100644
--- a/package.json
+++ b/package.json
@@ -12,9 +12,9 @@
   },
   "scripts": {
     "lint": "tslint src/**/*.ts",
-    "build": "cpx \"src/**/*.{vert,frag,glsl,scss,woff,woff2,ttf,otf,eot,svg,html}\" build/node_modules/ && tsc",
+    "build": "cpx \"src/**/*.{vert,frag,glsl,scss,woff,woff2,ttf,otf,eot,svg,html,gql}\" build/node_modules/ && tsc",
     "watch": "tsc -watch",
-    "watch-extra": "cpx \"src/**/*.{vert,frag,glsl,scss,woff,woff2,ttf,otf,eot,svg,html}\" build/node_modules/ --watch",
+    "watch-extra": "cpx \"src/**/*.{vert,frag,glsl,scss,woff,woff2,ttf,otf,eot,svg,html,gql}\" build/node_modules/ --watch",
     "watch-all-win": "start cmd /K npm run watch & start cmd /K npm run watch-extra & start cmd /K npm run watch-viewer & start http-server -p 1338",
     "test": "jest",
     "build-viewer": "webpack build/node_modules/apps/viewer/index.js --mode development -o build/viewer/index.js",
@@ -84,6 +84,9 @@
     "file-loader": "^1.1.11",
     "glslify-import": "^3.1.0",
     "glslify-loader": "^1.0.2",
+    "graphql-code-generator": "^0.10.5",
+    "graphql-codegen-typescript-template": "^0.10.5",
+    "graphql-tag": "^2.9.2",
     "jest": "^23.4.2",
     "jest-raw-loader": "^1.0.1",
     "mini-css-extract-plugin": "^0.4.1",
@@ -104,6 +107,8 @@
     "argparse": "^1.0.10",
     "compression": "^1.7.3",
     "express": "^4.16.3",
+    "graphql": "^0.13.2",
+    "graphql-request": "^1.8.0",
     "immutable": "^4.0.0-rc.9",
     "node-fetch": "^2.2.0",
     "react": "^16.4.2",
diff --git a/src/servers/model/properties.ts b/src/servers/model/properties.ts
index b3486a48d75c4f5dffb6e436637fa2367393a447..eafa41955940a0efc940e872b67bf8ab2fb1ac58 100644
--- a/src/servers/model/properties.ts
+++ b/src/servers/model/properties.ts
@@ -2,15 +2,18 @@
  * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
 import { Model } from 'mol-model/structure';
 import { StructureQualityReport } from './properties/structure-quality-report';
+import { SymmetryAnnotation } from './properties/rcsb/symmetry';
 
 export function attachModelProperties(model: Model): Promise<any>[] {
     // return a list of promises that start attaching the props in parallel
     // (if there are downloads etc.)
     return [
-        StructureQualityReport.attachFromPDBeApi(model)
+        StructureQualityReport.attachFromPDBeApi(model),
+        SymmetryAnnotation.attachFromRCSB(model)
     ];
 }
\ No newline at end of file
diff --git a/src/servers/model/properties/rcsb/graphql/symmetry.gql.ts b/src/servers/model/properties/rcsb/graphql/symmetry.gql.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0a01a456f7dca9eb8caf5a4cad9ae42eddcf4197
--- /dev/null
+++ b/src/servers/model/properties/rcsb/graphql/symmetry.gql.ts
@@ -0,0 +1,28 @@
+ // workaround so the query gets found by the codegen
+function gql (strs: TemplateStringsArray) { return strs.raw.join('') }
+
+export default
+gql`query RcsbSymmetry($pdbId: String) {
+    assemblies(pdbId: $pdbId) {
+        assembly_id
+        rcsb_annotation_symmetry {
+            source
+            symmetry_features {
+                type
+                clusters {
+                    avg_rmsd
+                    members
+                }
+                stoichiometry {
+                    description
+                    value
+                }
+                symmetry_axes {
+                    order
+                    start
+                    end
+                }
+            }
+        }
+    }
+}`
\ No newline at end of file
diff --git a/src/servers/model/properties/rcsb/graphql/types.ts b/src/servers/model/properties/rcsb/graphql/types.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5283fa12db8a479aabcdac07adcbc6740534bfe2
--- /dev/null
+++ b/src/servers/model/properties/rcsb/graphql/types.ts
@@ -0,0 +1,197 @@
+/* tslint:disable */
+/** Generated in 2018-08-03T15:19:31-07:00 */
+
+export enum PdbxLeavingAtomFlag {
+  N = "N",
+  Y = "Y"
+}
+
+export enum PdbxStereoConfig {
+  N = "N",
+  R = "R",
+  S = "S"
+}
+
+export enum ExperimentalSupport {
+  ASSAY_FOR_OLIGOMERIZATION = "ASSAY_FOR_OLIGOMERIZATION",
+  CROSS_LINKING = "CROSS_LINKING",
+  EQUILIBRIUM_CENTRIFUGATION = "EQUILIBRIUM_CENTRIFUGATION",
+  FLUORESCENCE_RESONANCE_ENERGY_TRANSFER = "FLUORESCENCE_RESONANCE_ENERGY_TRANSFER",
+  GEL_FILTRATION = "GEL_FILTRATION",
+  HOMOLOGY = "HOMOLOGY",
+  IMMUNOPRECIPITATION = "IMMUNOPRECIPITATION",
+  ISOTHERMAL_TITRATION_CALORIMETRY = "ISOTHERMAL_TITRATION_CALORIMETRY",
+  LIGHT_SCATTERING = "LIGHT_SCATTERING",
+  MASS_SPECTROMETRY = "MASS_SPECTROMETRY",
+  MICROSCOPY = "MICROSCOPY",
+  NATIVE_GEL_ELECTROPHORESIS = "NATIVE_GEL_ELECTROPHORESIS",
+  NONE = "NONE",
+  SAXS = "SAXS",
+  SCANNING_TRANSMISSION_ELECTRON_MICROSCOPY = "SCANNING_TRANSMISSION_ELECTRON_MICROSCOPY",
+  SURFACE_PLASMON_RESONANCE = "SURFACE_PLASMON_RESONANCE"
+}
+
+export enum Type {
+  GLOBAL = "GLOBAL",
+  LOCAL = "LOCAL",
+  PSEUDO = "PSEUDO"
+}
+
+export enum UnpublishedFlag {
+  N = "N",
+  Y = "Y"
+}
+
+export enum PdbxDiffrnProtocol {
+  LAUE = "LAUE",
+  MAD = "MAD",
+  SINGLE_WAVELENGTH = "SINGLE_WAVELENGTH"
+}
+
+export enum PdbxMonochromaticOrLaueML {
+  L = "L",
+  M = "M"
+}
+
+export enum PdbxScatteringType {
+  ELECTRON = "ELECTRON",
+  NEUTRON = "NEUTRON",
+  X_RAY = "X_RAY"
+}
+
+export enum Source {
+  ELECTRON_MICROSCOPE = "ELECTRON_MICROSCOPE",
+  FREE_ELECTRON_LASER = "FREE_ELECTRON_LASER",
+  LIQUID_ANODE = "LIQUID_ANODE",
+  NUCLEAR_REACTOR = "NUCLEAR_REACTOR",
+  ROTATING_ANODE = "ROTATING_ANODE",
+  SEALED_TUBE = "SEALED_TUBE",
+  SPALLATION_SOURCE = "SPALLATION_SOURCE",
+  SYNCHROTRON = "SYNCHROTRON"
+}
+
+export enum RefSpace {
+  REAL = "REAL",
+  RECIPROCAL = "RECIPROCAL"
+}
+
+export enum SymmetryType {
+  HELICAL = "HELICAL",
+  POINT = "POINT",
+  _2_D_CRYSTAL = "_2_D_CRYSTAL",
+  _3_D_CRYSTAL = "_3_D_CRYSTAL"
+}
+
+export enum AggregationState {
+  CELL = "CELL",
+  FILAMENT = "FILAMENT",
+  HELICAL_ARRAY = "HELICAL_ARRAY",
+  PARTICLE = "PARTICLE",
+  TISSUE = "TISSUE",
+  _2_D_ARRAY = "_2_D_ARRAY",
+  _3_D_ARRAY = "_3_D_ARRAY"
+}
+
+export enum ReconstructionMethod {
+  CRYSTALLOGRAPHY = "CRYSTALLOGRAPHY",
+  HELICAL = "HELICAL",
+  SINGLE_PARTICLE = "SINGLE_PARTICLE",
+  SUBTOMOGRAM_AVERAGING = "SUBTOMOGRAM_AVERAGING",
+  TOMOGRAPHY = "TOMOGRAPHY"
+}
+
+export enum EmbeddingApplied {
+  NO = "NO",
+  YES = "YES"
+}
+
+export enum StainingApplied {
+  NO = "NO",
+  YES = "YES"
+}
+
+export enum VitrificationApplied {
+  NO = "NO",
+  YES = "YES"
+}
+
+export enum SrcMethod {
+  MAN = "MAN",
+  NAT = "NAT",
+  SYN = "SYN"
+}
+
+export enum RcsbType {
+  DNA = "DNA",
+  HYBRID = "HYBRID",
+  OTHER = "OTHER",
+  POLYPEPTIDE = "POLYPEPTIDE",
+  RNA = "RNA"
+}
+
+export enum Level {
+  _100 = "_100",
+  _30 = "_30",
+  _40 = "_40",
+  _50 = "_50",
+  _60 = "_60",
+  _70 = "_70",
+  _80 = "_80",
+  _90 = "_90",
+  _95 = "_95"
+}
+
+export enum PdbFormatCompatible {
+  N = "N",
+  Y = "Y"
+}
+
+export namespace RcsbSymmetry {
+  export type Variables = {
+    readonly pdbId?: string | null;
+  };
+
+  export type Query = {
+    readonly __typename?: "Query";
+    readonly assemblies?: ReadonlyArray<Assemblies | null> | null;
+  };
+
+  export type Assemblies = {
+    readonly __typename?: "CoreAssembly";
+    readonly assembly_id?: number | null;
+    readonly rcsb_annotation_symmetry?: RcsbAnnotationSymmetry | null;
+  };
+
+  export type RcsbAnnotationSymmetry = {
+    readonly __typename?: "RcsbAnnotationSymmetry";
+    readonly source?: string | null;
+    readonly symmetry_features?: ReadonlyArray<SymmetryFeatures | null> | null;
+  };
+
+  export type SymmetryFeatures = {
+    readonly __typename?: "SymmetryFeature";
+    readonly type?: Type | null;
+    readonly clusters?: ReadonlyArray<Clusters | null> | null;
+    readonly stoichiometry?: Stoichiometry | null;
+    readonly symmetry_axes?: ReadonlyArray<SymmetryAxes | null> | null;
+  };
+
+  export type Clusters = {
+    readonly __typename?: "Cluster";
+    readonly avg_rmsd?: number | null;
+    readonly members?: ReadonlyArray<string | null> | null;
+  };
+
+  export type Stoichiometry = {
+    readonly __typename?: "Stoichiometry";
+    readonly description?: string | null;
+    readonly value?: ReadonlyArray<string | null> | null;
+  };
+
+  export type SymmetryAxes = {
+    readonly __typename?: "SymmetryAxis";
+    readonly order?: number | null;
+    readonly start?: ReadonlyArray<number | null> | null;
+    readonly end?: ReadonlyArray<number | null> | null;
+  };
+}
diff --git a/src/servers/model/properties/rcsb/symmetry.ts b/src/servers/model/properties/rcsb/symmetry.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e34934c8d5e374ea45c1dfc8ed26ca8e75f872b1
--- /dev/null
+++ b/src/servers/model/properties/rcsb/symmetry.ts
@@ -0,0 +1,150 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { GraphQLClient } from 'graphql-request'
+
+import { RcsbSymmetry } from './graphql/types';
+import query from './graphql/symmetry.gql';
+
+import { Model, ModelPropertyDescriptor } from 'mol-model/structure';
+import { CifWriter } from 'mol-io/writer/cif';
+import { Database as _Database, Column, Table } from 'mol-data/db'
+import { Category } from 'mol-io/writer/cif/encoder';
+import { Tensor } from 'mol-math/linear-algebra';
+import { CifExportContext } from 'mol-model/structure/export/mmcif';
+
+const { str, int, float, Aliased, Vector, List } = Column.Schema;
+
+function getInstance(name: keyof SymmetryAnnotation.Schema): (ctx: CifExportContext) => CifWriter.Category.Instance<any, any> {
+    return function(ctx: CifExportContext) {
+        const db = SymmetryAnnotation.get(ctx.model);
+        return db ? Category.ofTable(db[name]) : CifWriter.Category.Empty;
+    }
+}
+
+function getCategory(name: keyof SymmetryAnnotation.Schema) {
+    return { name, instance: getInstance(name) }
+}
+
+function createDatabase(assemblies: ReadonlyArray<RcsbSymmetry.Assemblies>): SymmetryAnnotation.Database {
+    const Schema = SymmetryAnnotation.Schema
+
+    const featureRows: Table.Row<typeof Schema.symmetry_annotation_feature>[] = []
+    const clusterRows: Table.Row<typeof Schema.symmetry_annotation_cluster>[] = []
+    const axisRows: Table.Row<typeof Schema.symmetry_annotation_axis>[] = []
+
+    let id = 0
+    for (let i = 0, il = assemblies.length; i < il; ++i) {
+        const a = assemblies[i]
+        const assembly_id = (a.assembly_id!).toString()
+        const source = a.rcsb_annotation_symmetry!.source!
+        const symmetry_features = a.rcsb_annotation_symmetry!.symmetry_features!
+        for (let j = 0, jl = symmetry_features.length; j < jl; ++j) {
+            const f = symmetry_features[j]!
+            featureRows.push({
+                id,
+                assembly_id,
+                source,
+                type: f.type!,
+                stoichiometry_value: (f.stoichiometry!.value!) as string[],
+                stoichiometry_description: f.stoichiometry!.description!
+            })
+
+            const clusters = f.clusters
+            if (clusters) {
+                for (let k = 0, kl = clusters.length; k < kl; ++k) {
+                    const c = clusters[k]!
+                    clusterRows.push({
+                        feature_id: id,
+                        avg_rmsd: c.avg_rmsd!,
+                        members: c.members as string[]
+                    })
+                }
+            }
+
+            const axes = f.symmetry_axes
+            if (axes) {
+                for (let k = 0, kl = axes.length; k < kl; ++k) {
+                    const a = axes[k]!
+                    axisRows.push({
+                        feature_id: id,
+                        order: a.order!,
+                        start: a.start as Tensor.Data,
+                        end: a.end as Tensor.Data
+                    })
+                }
+            }
+
+            id += 1
+        }
+    }
+
+    return _Database.ofTables('symmetry_annotation', Schema, {
+        symmetry_annotation_feature: Table.ofRows(Schema.symmetry_annotation_feature, featureRows),
+        symmetry_annotation_cluster: Table.ofRows(Schema.symmetry_annotation_cluster, clusterRows),
+        symmetry_annotation_axis: Table.ofRows(Schema.symmetry_annotation_axis, axisRows)
+    })
+}
+
+const _Descriptor: ModelPropertyDescriptor = {
+    isStatic: true,
+    name: 'symmetry_annotation',
+    cifExport: {
+        categories: [
+            getCategory('symmetry_annotation_feature'),
+            getCategory('symmetry_annotation_cluster'),
+            getCategory('symmetry_annotation_axis')
+        ]
+    }
+}
+
+const client = new GraphQLClient('http://rest-experimental.rcsb.org/graphql')
+
+export namespace SymmetryAnnotation {
+    export const Schema = {
+        symmetry_annotation_feature: {
+            id: int,
+            assembly_id: str,
+            source: str,
+            type: Aliased<'GLOBAL' | 'LOCAL' | 'PSEUDO'>(str),
+            stoichiometry_value: List(',', x => x),
+            stoichiometry_description: str
+        },
+        symmetry_annotation_cluster: {
+            feature_id: int,
+            avg_rmsd: float,
+            members: List(',', x => x)
+        },
+        symmetry_annotation_axis: {
+            feature_id: int,
+            order: int,
+            start: Vector(3),
+            end: Vector(3)
+        }
+    }
+    export type Schema = typeof Schema
+    export interface Database extends _Database<Schema> {}
+
+    export const Descriptor = _Descriptor;
+
+    export async function attachFromRCSB(model: Model) {
+        if (model.customProperties.has(Descriptor)) return true;
+
+        const variables: RcsbSymmetry.Variables = { pdbId: model.label.toLowerCase() };
+        const result = await client.request<RcsbSymmetry.Query>(query, variables);
+        if (!result || !result.assemblies) return false;
+
+        const db: Database = createDatabase(result.assemblies as ReadonlyArray<RcsbSymmetry.Assemblies>)
+        model.customProperties.add(Descriptor);
+        model._staticPropertyData.__SymmetryAnnotation__ = db;
+
+        return true;
+    }
+
+    export function get(model: Model): Database | undefined {
+        return model._staticPropertyData.__SymmetryAnnotation__;
+    }
+}
\ No newline at end of file
diff --git a/src/servers/model/test.ts b/src/servers/model/test.ts
index 882c18b6afc6095f813f4f12c2e5e48bd07b0d3a..14bafb1bac02f982ded8770439dc5e5532bcf5c8 100644
--- a/src/servers/model/test.ts
+++ b/src/servers/model/test.ts
@@ -1,5 +1,6 @@
 import { resolveJob } from './server/query';
 import * as fs from 'fs'
+import * as path from 'path'
 import { StructureCache } from './server/structure-wrapper';
 import { createJob } from './server/jobs';
 
@@ -33,13 +34,25 @@ function wrapFile(fn: string) {
     return w;
 }
 
+const basePath = path.join(__dirname, '..', '..', '..', '..')
+const examplesPath = path.join(basePath, 'examples')
+const outPath = path.join(basePath, 'build', 'test')
+if (!fs.existsSync(outPath)) fs.mkdirSync(outPath);
+
 async function run() {
     try {
-        const request = createJob('_local_', 'e:/test/quick/1cbs_updated.cif', 'residueInteraction', { label_comp_id: 'REA' });
+        // const request = createJob('_local_', 'e:/test/quick/1cbs_updated.cif', 'residueInteraction', { label_comp_id: 'REA' });
+        // const encoder = await resolveJob(request);
+        // const writer = wrapFile('e:/test/mol-star/1cbs_full.cif');
+        // const testFile = '1crn.cif'
+        const testFile = '1grm_updated.cif'
+        const request = createJob('_local_', path.join(examplesPath, testFile), 'full', {});
         const encoder = await resolveJob(request);
-        const writer = wrapFile('e:/test/mol-star/1cbs_full.cif');
+        const writer = wrapFile(path.join(outPath, testFile));
         encoder.writeTo(writer);
         writer.end();
+    } catch (e) {
+        console.error(e)
     } finally {
         StructureCache.expireAll();
     }