diff --git a/README b/README
index 3bb211341..2536c74ae 100644
--- a/README
+++ b/README
@@ -2644,7 +2644,7 @@ nonbreaking space after the image:
 
 #### Extension: `common_link_attributes` ####
 
-Attributes can be set on images:
+Attributes can be set on links and images:
 
     An inline ![image](foo.jpg){#id .class width=30 height=20px}
     and a reference ![image][ref] with attributes.
@@ -2666,7 +2666,7 @@ and `%`. There must not be any spaces between the number and the unit.
 For example:
 
 ```
-![](file.jpg){width=50%}
+![](file.jpg){ width=50% }
 ```
 
 - Dimensions are converted to inches for output in page-based formats like
@@ -2685,10 +2685,6 @@ For example:
   is to look at the image resolution and the dpi metadata embedded in
   the image file.
 
-Note that while attributes are also parsed on links, pandoc's internal
-document model provides nowhere to put them, so they are presently
-just ignored.
-
 
 Footnotes
 ---------
@@ -2964,9 +2960,8 @@ letters are omitted.
 #### Extension: `link_attributes` ####
 
 Parses multimarkdown style key-value attributes on link
-and image references. (Since pandoc's internal document model
-provides nowhere to put these for links, they are presently just
-ignored, but they work for images.)
+and image references. This extension should not be confused with the
+[`common_link_attributes`](#extension-common_link_attributes) extension.
 
     This is a reference ![image][ref] with multimarkdown attributes.
 
diff --git a/src/Text/Pandoc/Readers/CommonMark.hs b/src/Text/Pandoc/Readers/CommonMark.hs
index 9112979ab..7f752c446 100644
--- a/src/Text/Pandoc/Readers/CommonMark.hs
+++ b/src/Text/Pandoc/Readers/CommonMark.hs
@@ -113,7 +113,7 @@ addInline (Node _ EMPH nodes) =
 addInline (Node _ STRONG nodes) =
   (Strong (addInlines nodes) :)
 addInline (Node _ (LINK url title) nodes) =
-  (Link (addInlines nodes) (unpack url, unpack title) :)
+  (Link nullAttr (addInlines nodes) (unpack url, unpack title) :)
 addInline (Node _ (IMAGE url title) nodes) =
   (Image nullAttr (addInlines nodes) (unpack url, unpack title) :)
 addInline _ = id
diff --git a/src/Text/Pandoc/Readers/DocBook.hs b/src/Text/Pandoc/Readers/DocBook.hs
index cbd50c252..db438e26d 100644
--- a/src/Text/Pandoc/Readers/DocBook.hs
+++ b/src/Text/Pandoc/Readers/DocBook.hs
@@ -967,7 +967,8 @@ parseInline (Elem e) =
                                Just h -> h
                                _      -> ('#' : attrValue "linkend" e)
              let ils' = if ils == mempty then str href else ils
-             return $ link href "" ils'
+             let attr = (attrValue "id" e, words $ attrValue "role" e, [])
+             return $ linkWith href "" attr ils'
         "foreignphrase" -> emph <$> innerInlines
         "emphasis" -> case attrValue "role" e of
                              "bold"   -> strong <$> innerInlines
diff --git a/src/Text/Pandoc/Readers/Docx.hs b/src/Text/Pandoc/Readers/Docx.hs
index 67a97ae85..b80280553 100644
--- a/src/Text/Pandoc/Readers/Docx.hs
+++ b/src/Text/Pandoc/Readers/Docx.hs
@@ -533,10 +533,10 @@ bodyPartToBlocks (OMathPara e) = do
 
 -- replace targets with generated anchors.
 rewriteLink' :: Inline -> DocxContext Inline
-rewriteLink' l@(Link ils ('#':target, title)) = do
+rewriteLink' l@(Link attr ils ('#':target, title)) = do
   anchorMap <- gets docxAnchorMap
   return $ case M.lookup target anchorMap of
-    Just newTarget -> (Link ils ('#':newTarget, title))
+    Just newTarget -> (Link attr ils ('#':newTarget, title))
     Nothing        -> l
 rewriteLink' il = return il
 
diff --git a/src/Text/Pandoc/Readers/EPUB.hs b/src/Text/Pandoc/Readers/EPUB.hs
index 04edf4c6a..fb86f1286 100644
--- a/src/Text/Pandoc/Readers/EPUB.hs
+++ b/src/Text/Pandoc/Readers/EPUB.hs
@@ -192,20 +192,20 @@ fixInlineIRs s (Span as v) =
   Span (fixAttrs s as) v
 fixInlineIRs s (Code as code) =
   Code (fixAttrs s as) code
-fixInlineIRs s (Link t ('#':url, tit)) =
-  Link t (addHash s url, tit)
+fixInlineIRs s (Link attr t ('#':url, tit)) =
+  Link attr t (addHash s url, tit)
 fixInlineIRs _ v = v
 
 normalisePath :: Inline -> Inline
-normalisePath (Link t (url, tit)) =
+normalisePath (Link attr t (url, tit)) =
   let (path, uid) = span (/= '#') url in
-  Link t (takeFileName path ++ uid, tit)
+  Link attr t (takeFileName path ++ uid, tit)
 normalisePath s = s
 
 prependHash :: [String] -> Inline -> Inline
-prependHash ps l@(Link is (url, tit))
+prependHash ps l@(Link attr is (url, tit))
   | or [s `isPrefixOf` url | s <- ps] =
-    Link is ('#':url, tit)
+    Link attr is ('#':url, tit)
   | otherwise = l
 prependHash _ i = i
 
diff --git a/src/Text/Pandoc/Readers/HTML.hs b/src/Text/Pandoc/Readers/HTML.hs
index d0ee893f2..5a93e0d5b 100644
--- a/src/Text/Pandoc/Readers/HTML.hs
+++ b/src/Text/Pandoc/Readers/HTML.hs
@@ -576,16 +576,8 @@ pLineBreak = do
   return B.linebreak
 
 pLink :: TagParser Inlines
-pLink = pRelLink <|> pAnchor
-
-pAnchor :: TagParser Inlines
-pAnchor = try $ do
-  tag <- pSatisfy (tagOpenLit "a" (isJust . lookup "id"))
-  return $ B.spanWith (fromAttrib "id" tag , [], []) mempty
-
-pRelLink :: TagParser Inlines
-pRelLink = try $ do
-  tag <- pSatisfy (tagOpenLit "a" (isJust . lookup "href"))
+pLink = try $ do
+  tag <- pSatisfy $ tagOpenLit "a" (const True)
   mbBaseHref <- baseHref <$> getState
   let url' = fromAttrib "href" tag
   let url = case (isURI url', mbBaseHref) of
@@ -593,11 +585,9 @@ pRelLink = try $ do
                  _               -> url'
   let title = fromAttrib "title" tag
   let uid = fromAttrib "id" tag
-  let spanC = case uid of
-              [] -> id
-              s  -> B.spanWith (s, [], [])
+  let cls = words $ fromAttrib "class" tag
   lab <- trimInlines . mconcat <$> manyTill inline (pCloses "a")
-  return $ spanC $ B.link (escapeURI url) title lab
+  return $ B.linkWith (escapeURI url) title (uid, cls, []) lab
 
 pImage :: TagParser Inlines
 pImage = do
diff --git a/src/Text/Pandoc/Readers/RST.hs b/src/Text/Pandoc/Readers/RST.hs
index 8969c3176..4138d65ea 100644
--- a/src/Text/Pandoc/Readers/RST.hs
+++ b/src/Text/Pandoc/Readers/RST.hs
@@ -814,7 +814,7 @@ substKey = try $ do
              -- use alt unless :alt: attribute on image:
              [Para [Image _ [Str "image"] (src,tit)]] ->
                 return $ B.image src tit alt
-             [Para [Link [Image _ [Str "image"] (src,tit)] (src',tit')]] ->
+             [Para [Link _ [Image _ [Str "image"] (src,tit)] (src',tit')]] ->
                 return $ B.link src' tit' (B.image src tit alt)
              [Para ils] -> return $ B.fromList ils
              _          -> mzero
diff --git a/src/Text/Pandoc/Shared.hs b/src/Text/Pandoc/Shared.hs
index a816af8b9..a86e5da95 100644
--- a/src/Text/Pandoc/Shared.hs
+++ b/src/Text/Pandoc/Shared.hs
@@ -507,8 +507,8 @@ normalizeInlines (Note bs : ys) = Note (normalizeBlocks bs) :
   normalizeInlines ys
 normalizeInlines (Quoted qt ils : ys) =
   Quoted qt (normalizeInlines ils) : normalizeInlines ys
-normalizeInlines (Link ils t : ys) =
-  Link (normalizeInlines ils) t : normalizeInlines ys
+normalizeInlines (Link attr ils t : ys) =
+  Link attr (normalizeInlines ils) t : normalizeInlines ys
 normalizeInlines (Image attr ils t : ys) =
   Image attr (normalizeInlines ils) t : normalizeInlines ys
 normalizeInlines (Cite cs ils : ys) =
diff --git a/src/Text/Pandoc/Writers/AsciiDoc.hs b/src/Text/Pandoc/Writers/AsciiDoc.hs
index 8b36ef5c6..4e8c96907 100644
--- a/src/Text/Pandoc/Writers/AsciiDoc.hs
+++ b/src/Text/Pandoc/Writers/AsciiDoc.hs
@@ -394,7 +394,7 @@ inlineToAsciiDoc _ (RawInline f s)
 inlineToAsciiDoc _ (LineBreak) = return $ " +" <> cr
 inlineToAsciiDoc _ Space = return space
 inlineToAsciiDoc opts (Cite _ lst) = inlineListToAsciiDoc opts lst
-inlineToAsciiDoc opts (Link txt (src, _tit)) = do
+inlineToAsciiDoc opts (Link _ txt (src, _tit)) = do
 -- relative:  link:downloads/foo.zip[download foo.zip]
 -- abs:  http://google.cod[Google]
 -- or my@email.com[email john]
diff --git a/src/Text/Pandoc/Writers/CommonMark.hs b/src/Text/Pandoc/Writers/CommonMark.hs
index c65b8de37..c2d476641 100644
--- a/src/Text/Pandoc/Writers/CommonMark.hs
+++ b/src/Text/Pandoc/Writers/CommonMark.hs
@@ -153,7 +153,7 @@ inlineToNodes (SmallCaps xs) =
   ((node (INLINE_HTML (T.pack "<span style=\"font-variant:small-caps;\">")) []
     : inlinesToNodes xs ++
     [node (INLINE_HTML (T.pack "</span>")) []]) ++ )
-inlineToNodes (Link ils (url,tit)) =
+inlineToNodes (Link _ ils (url,tit)) =
   (node (LINK (T.pack url) (T.pack tit)) (inlinesToNodes ils) :)
 inlineToNodes (Image _ ils (url,tit)) =
   (node (IMAGE (T.pack url) (T.pack tit)) (inlinesToNodes ils) :)
diff --git a/src/Text/Pandoc/Writers/ConTeXt.hs b/src/Text/Pandoc/Writers/ConTeXt.hs
index 97f61dac8..56fcd4b0b 100644
--- a/src/Text/Pandoc/Writers/ConTeXt.hs
+++ b/src/Text/Pandoc/Writers/ConTeXt.hs
@@ -303,7 +303,7 @@ inlineToConTeXt (RawInline _ _) = return empty
 inlineToConTeXt (LineBreak) = return $ text "\\crlf" <> cr
 inlineToConTeXt Space = return space
 -- Handle HTML-like internal document references to sections
-inlineToConTeXt (Link txt          (('#' : ref), _)) = do
+inlineToConTeXt (Link _ txt (('#' : ref), _)) = do
   opts <- gets stOptions
   contents <-  inlineListToConTeXt txt
   let ref' = toLabel $ stringToConTeXt opts ref
@@ -311,7 +311,7 @@ inlineToConTeXt (Link txt          (('#' : ref), _)) = do
            <> braces contents
            <> brackets (text ref')
 
-inlineToConTeXt (Link txt          (src, _))      = do
+inlineToConTeXt (Link _ txt (src, _)) = do
   let isAutolink = txt == [Str (unEscapeString src)]
   st <- get
   let next = stNextRef st
@@ -326,10 +326,9 @@ inlineToConTeXt (Link txt          (src, _))      = do
                   else brackets empty <> brackets contents)
            <> "\\from"
            <> brackets (text ref)
-inlineToConTeXt (Image attr _ (src, _)) = do
+inlineToConTeXt (Image attr@(_,cls,_) _ (src, _)) = do
   opts <- gets stOptions
-  let (_,cls,_) = attr
-      showDim dir = let d = text (show dir) <> "="
+  let showDim dir = let d = text (show dir) <> "="
                     in case (dimension dir attr) of
                          Just (Pixel a)   ->
                            [d <> text (showInInch opts (Pixel a)) <> "in"]
diff --git a/src/Text/Pandoc/Writers/Custom.hs b/src/Text/Pandoc/Writers/Custom.hs
index 18b1bec5f..8f2810932 100644
--- a/src/Text/Pandoc/Writers/Custom.hs
+++ b/src/Text/Pandoc/Writers/Custom.hs
@@ -309,7 +309,7 @@ inlineToCustom lua (RawInline format str) =
 
 inlineToCustom lua (LineBreak) = callfunc lua "LineBreak"
 
-inlineToCustom lua (Link txt (src,tit)) =
+inlineToCustom lua (Link _ txt (src,tit)) =
   callfunc lua "Link" txt src tit
 
 inlineToCustom lua (Image _ alt (src,tit)) =
diff --git a/src/Text/Pandoc/Writers/Docbook.hs b/src/Text/Pandoc/Writers/Docbook.hs
index af289d45e..e3444d257 100644
--- a/src/Text/Pandoc/Writers/Docbook.hs
+++ b/src/Text/Pandoc/Writers/Docbook.hs
@@ -153,16 +153,9 @@ listItemToDocbook opts item =
   inTagsIndented "listitem" $ blocksToDocbook opts $ map plainToPara item
 
 imageToDocbook :: WriterOptions -> Attr -> String -> Doc
-imageToDocbook _ attr src = selfClosingTag "imagedata" $ ("fileref", src):ident
-  ++ roles ++ dims
+imageToDocbook _ attr src = selfClosingTag "imagedata" $
+  ("fileref", src) : idAndRole attr ++ dims
   where
-    (idStr,cls,_) = attr
-    ident = if null idStr
-               then []
-               else [("id", idStr)]
-    roles = if null cls
-               then []
-               else [("role", unwords cls)]
     dims = go Width "width" ++ go Height "depth"
     go dir dstr = case (dimension dir attr) of
                     Just a  -> [(dstr, show a)]
@@ -339,7 +332,7 @@ inlineToDocbook _ (RawInline f x) | f == "html" || f == "docbook" = text x
                                   | otherwise                     = empty
 inlineToDocbook _ LineBreak = text "\n"
 inlineToDocbook _ Space = space
-inlineToDocbook opts (Link txt (src, _))
+inlineToDocbook opts (Link attr txt (src, _))
   | Just email <- stripPrefix "mailto:" src =
       let emailLink = inTagsSimple "email" $ text $
                       escapeStringForXML $ email
@@ -349,8 +342,8 @@ inlineToDocbook opts (Link txt (src, _))
                               char '(' <> emailLink <> char ')'
   | otherwise =
       (if isPrefixOf "#" src
-            then inTags False "link" [("linkend", drop 1 src)]
-            else inTags False "ulink" [("url", src)]) $
+            then inTags False "link" $ ("linkend", drop 1 src) : idAndRole attr
+            else inTags False "ulink" $ ("url", src) : idAndRole attr ) $
         inlinesToDocbook opts txt
 inlineToDocbook opts (Image attr _ (src, tit)) =
   let titleDoc = if null tit
@@ -365,3 +358,14 @@ inlineToDocbook opts (Note contents) =
 isMathML :: HTMLMathMethod -> Bool
 isMathML (MathML _) = True
 isMathML _          = False
+
+idAndRole :: Attr -> [(String, String)]
+idAndRole (id',cls,_) = ident ++ role
+  where
+    ident = if null id'
+               then []
+               else [("id", id')]
+    role  = if null cls
+               then []
+               else [("role", unwords cls)]
+
diff --git a/src/Text/Pandoc/Writers/Docx.hs b/src/Text/Pandoc/Writers/Docx.hs
index a17be3ca0..e9f256210 100644
--- a/src/Text/Pandoc/Writers/Docx.hs
+++ b/src/Text/Pandoc/Writers/Docx.hs
@@ -1087,11 +1087,11 @@ inlineToOpenXML opts (Note bs) = do
            [ mknode "w:rPr" [] footnoteStyle
            , mknode "w:footnoteReference" [("w:id", notenum)] () ] ]
 -- internal link:
-inlineToOpenXML opts (Link txt ('#':xs,_)) = do
+inlineToOpenXML opts (Link _ txt ('#':xs,_)) = do
   contents <- withTextPropM (rStyleM "Hyperlink") $ inlinesToOpenXML opts txt
   return [ mknode "w:hyperlink" [("w:anchor",xs)] contents ]
 -- external link:
-inlineToOpenXML opts (Link txt (src,_)) = do
+inlineToOpenXML opts (Link _ txt (src,_)) = do
   contents <- withTextPropM (rStyleM "Hyperlink") $ inlinesToOpenXML opts txt
   extlinks <- gets stExternalLinks
   id' <- case M.lookup src extlinks of
diff --git a/src/Text/Pandoc/Writers/DokuWiki.hs b/src/Text/Pandoc/Writers/DokuWiki.hs
index 915821050..ebd5f8d70 100644
--- a/src/Text/Pandoc/Writers/DokuWiki.hs
+++ b/src/Text/Pandoc/Writers/DokuWiki.hs
@@ -464,13 +464,13 @@ inlineToDokuWiki _ (LineBreak) = return "\\\\\n"
 
 inlineToDokuWiki _ Space = return " "
 
-inlineToDokuWiki opts (Link txt (src, _)) = do
+inlineToDokuWiki opts (Link _ txt (src, _)) = do
   label <- inlineListToDokuWiki opts txt
   case txt of
      [Str s] | "mailto:" `isPrefixOf` src -> return $ "<" ++ s ++ ">"
              | escapeURI s == src -> return src
      _  -> if isURI src
-              then return $ "[[" ++ src ++ "|" ++ label ++ "]]"
+              then return $ "[[" ++ src  ++ "|" ++ label ++ "]]"
               else return $ "[[" ++ src' ++ "|" ++ label ++ "]]"
                      where src' = case src of
                                      '/':xs -> xs  -- with leading / it's a
diff --git a/src/Text/Pandoc/Writers/EPUB.hs b/src/Text/Pandoc/Writers/EPUB.hs
index 950d5cde3..c3e295c8f 100644
--- a/src/Text/Pandoc/Writers/EPUB.hs
+++ b/src/Text/Pandoc/Writers/EPUB.hs
@@ -457,10 +457,10 @@ writeEPUB opts doc@(Pandoc meta _) = do
                           chapters' [1..]
 
   let fixInternalReferences :: Inline -> Inline
-      fixInternalReferences (Link lab ('#':xs, tit)) =
+      fixInternalReferences (Link attr lab ('#':xs, tit)) =
         case lookup xs reftable of
-             Just ys ->  Link lab (ys, tit)
-             Nothing -> Link lab ('#':xs, tit)
+             Just ys -> Link attr lab (ys, tit)
+             Nothing -> Link attr lab ('#':xs, tit)
       fixInternalReferences x = x
 
   -- internal reference IDs change when we chunk the file,
diff --git a/src/Text/Pandoc/Writers/FB2.hs b/src/Text/Pandoc/Writers/FB2.hs
index f8f007185..bc936fce5 100644
--- a/src/Text/Pandoc/Writers/FB2.hs
+++ b/src/Text/Pandoc/Writers/FB2.hs
@@ -442,7 +442,7 @@ toXml Space = return [txt " "]
 toXml LineBreak = return [el "empty-line" ()]
 toXml (Math _ formula) = insertMath InlineImage formula
 toXml (RawInline _ _) = return []  -- raw TeX and raw HTML are suppressed
-toXml (Link text (url,ttl)) = do
+toXml (Link _ text (url,ttl)) = do
   fns <- footnotes `liftM` get
   let n = 1 + length fns
   let ln_id = linkID n
@@ -572,7 +572,7 @@ plain Space = " "
 plain LineBreak = "\n"
 plain (Math _ s) = s
 plain (RawInline _ s) = s
-plain (Link text (url,_)) = concat (map plain text ++ [" <", url, ">"])
+plain (Link _ text (url,_)) = concat (map plain text ++ [" <", url, ">"])
 plain (Image _ alt _) = concat (map plain alt)
 plain (Note _) = ""  -- FIXME
 
diff --git a/src/Text/Pandoc/Writers/HTML.hs b/src/Text/Pandoc/Writers/HTML.hs
index 436c5b343..ab158b38d 100644
--- a/src/Text/Pandoc/Writers/HTML.hs
+++ b/src/Text/Pandoc/Writers/HTML.hs
@@ -363,10 +363,10 @@ parseMailto s = do
        _ -> fail "not a mailto: URL"
 
 -- | Obfuscate a "mailto:" link.
-obfuscateLink :: WriterOptions -> Html -> String -> Html
-obfuscateLink opts txt s | writerEmailObfuscation opts == NoObfuscation =
-  H.a ! A.href (toValue s) $ txt
-obfuscateLink opts (renderHtml -> txt) s =
+obfuscateLink :: WriterOptions -> Attr -> Html -> String -> Html
+obfuscateLink opts attr txt s | writerEmailObfuscation opts == NoObfuscation =
+  addAttrs opts attr $ H.a ! A.href (toValue s) $ txt
+obfuscateLink opts attr (renderHtml -> txt) s =
   let meth = writerEmailObfuscation opts
       s' = map toLower (take 7 s) ++ drop 7 s
   in  case parseMailto s' of
@@ -392,7 +392,7 @@ obfuscateLink opts (renderHtml -> txt) s =
                      linkText  ++ "+'<\\/'+'a'+'>');\n// -->\n")) >>
                      H.noscript (preEscapedString $ obfuscateString altText)
                 _ -> error $ "Unknown obfuscation method: " ++ show meth
-        _ -> H.a ! A.href (toValue s) $ toHtml txt  -- malformed email
+        _ -> addAttrs opts attr $ H.a ! A.href (toValue s) $ toHtml txt  -- malformed email
 
 -- | Obfuscate character as entity.
 obfuscateChar :: Char -> String
@@ -808,10 +808,10 @@ inlineToHtml opts inline =
                                _             -> return mempty
       | f == Format "html" -> return $ preEscapedString str
       | otherwise          -> return mempty
-    (Link txt (s,_)) | "mailto:" `isPrefixOf` s -> do
+    (Link attr txt (s,_)) | "mailto:" `isPrefixOf` s -> do
                         linkText <- inlineListToHtml opts txt
-                        return $ obfuscateLink opts linkText s
-    (Link txt (s,tit)) -> do
+                        return $ obfuscateLink opts attr linkText s
+    (Link attr txt (s,tit)) -> do
                         linkText <- inlineListToHtml opts txt
                         let s' = case s of
                                       '#':xs | writerSlideVariant opts ==
@@ -821,9 +821,10 @@ inlineToHtml opts inline =
                         let link' = if txt == [Str (unEscapeString s)]
                                        then link ! A.class_ "uri"
                                        else link
+                        let link'' = addAttrs opts attr link'
                         return $ if null tit
-                                    then link'
-                                    else link' ! A.title (toValue tit)
+                                    then link''
+                                    else link'' ! A.title (toValue tit)
     (Image attr txt (s,tit)) | treatAsImage s -> do
                         let alternate' = stringify txt
                         let attributes = [A.src $ toValue s] ++
@@ -874,7 +875,7 @@ blockListToNote :: WriterOptions -> String -> [Block] -> State WriterState Html
 blockListToNote opts ref blocks =
   -- If last block is Para or Plain, include the backlink at the end of
   -- that block. Otherwise, insert a new Plain block with the backlink.
-  let backlink = [Link [Str "↩"] ("#" ++ writerIdentifierPrefix opts ++ "fnref" ++ ref,[])]
+  let backlink = [Link nullAttr [Str "↩"] ("#" ++ writerIdentifierPrefix opts ++ "fnref" ++ ref,[])]
       blocks'  = if null blocks
                     then []
                     else let lastBlock   = last blocks
diff --git a/src/Text/Pandoc/Writers/Haddock.hs b/src/Text/Pandoc/Writers/Haddock.hs
index 49a9953b6..a3188c647 100644
--- a/src/Text/Pandoc/Writers/Haddock.hs
+++ b/src/Text/Pandoc/Writers/Haddock.hs
@@ -327,7 +327,7 @@ inlineToHaddock _ (RawInline f str)
 inlineToHaddock _ (LineBreak) = return cr
 inlineToHaddock _ Space = return space
 inlineToHaddock opts (Cite _ lst) = inlineListToHaddock opts lst
-inlineToHaddock opts (Link txt (src, _)) = do
+inlineToHaddock opts (Link _ txt (src, _)) = do
   linktext <- inlineListToHaddock opts txt
   let useAuto = isURI src &&
                 case txt of
@@ -335,8 +335,8 @@ inlineToHaddock opts (Link txt (src, _)) = do
                       _                            -> False
   return $ nowrap $ "<" <> text src <>
            (if useAuto then empty else space <> linktext) <> ">"
-inlineToHaddock opts (Image _ alternate (source, tit)) = do
-  linkhaddock <- inlineToHaddock opts (Link alternate (source, tit))
+inlineToHaddock opts (Image attr alternate (source, tit)) = do
+  linkhaddock <- inlineToHaddock opts (Link attr alternate (source, tit))
   return $ "<" <> linkhaddock <> ">"
 -- haddock doesn't have notes, but we can fake it:
 inlineToHaddock opts (Note contents) = do
diff --git a/src/Text/Pandoc/Writers/ICML.hs b/src/Text/Pandoc/Writers/ICML.hs
index 71e541b6f..2bbd3b44f 100644
--- a/src/Text/Pandoc/Writers/ICML.hs
+++ b/src/Text/Pandoc/Writers/ICML.hs
@@ -419,7 +419,7 @@ inlineToICML _ style (Math _ str) = charStyle style $ text $ escapeStringForXML
 inlineToICML _ _ (RawInline f str)
   | f == Format "icml" = return $ text str
   | otherwise          = return empty
-inlineToICML opts style (Link lst (url, title)) = do
+inlineToICML opts style (Link _ lst (url, title)) = do
   content <- inlinesToICML opts (linkName:style) lst
   state $ \st ->
             let ident = if null $ links st
diff --git a/src/Text/Pandoc/Writers/LaTeX.hs b/src/Text/Pandoc/Writers/LaTeX.hs
index 76ad1c510..5857723a6 100644
--- a/src/Text/Pandoc/Writers/LaTeX.hs
+++ b/src/Text/Pandoc/Writers/LaTeX.hs
@@ -99,8 +99,8 @@ pandocToLaTeX options (Pandoc meta blocks) = do
                              _ -> blocks
                    else blocks
   -- see if there are internal links
-  let isInternalLink (Link _ ('#':xs,_))  = [xs]
-      isInternalLink _                    = []
+  let isInternalLink (Link _ _ ('#':xs,_))  = [xs]
+      isInternalLink _                      = []
   modify $ \s -> s{ stInternalLinks = query isInternalLink blocks' }
   let template = writerTemplate options
   -- set stBook depending on documentclass
@@ -620,8 +620,8 @@ defListItemToLaTeX (term, defs) = do
     term' <- inlineListToLaTeX term
     -- put braces around term if it contains an internal link,
     -- since otherwise we get bad bracket interactions: \item[\hyperref[..]
-    let isInternalLink (Link _ ('#':_,_)) = True
-        isInternalLink _                  = False
+    let isInternalLink (Link _ _ ('#':_,_)) = True
+        isInternalLink _                    = False
     let term'' = if any isInternalLink term
                     then braces term'
                     else term'
@@ -828,11 +828,11 @@ inlineToLaTeX (RawInline f str)
   | otherwise           = return empty
 inlineToLaTeX (LineBreak) = return $ "\\\\" <> cr
 inlineToLaTeX Space = return space
-inlineToLaTeX (Link txt ('#':ident, _)) = do
+inlineToLaTeX (Link _ txt ('#':ident, _)) = do
   contents <- inlineListToLaTeX txt
   lab <- toLabel ident
   return $ text "\\hyperref" <> brackets (text lab) <> braces contents
-inlineToLaTeX (Link txt (src, _)) =
+inlineToLaTeX (Link _ txt (src, _)) =
   case txt of
         [Str x] | escapeURI x == src ->  -- autolink
              do modify $ \s -> s{ stUrl = True }
diff --git a/src/Text/Pandoc/Writers/Man.hs b/src/Text/Pandoc/Writers/Man.hs
index 5a49428f6..71fd145e2 100644
--- a/src/Text/Pandoc/Writers/Man.hs
+++ b/src/Text/Pandoc/Writers/Man.hs
@@ -342,7 +342,7 @@ inlineToMan _ (RawInline f str)
 inlineToMan _ (LineBreak) = return $
   cr <> text ".PD 0" $$ text ".P" $$ text ".PD" <> cr
 inlineToMan _ Space = return space
-inlineToMan opts (Link txt (src, _)) = do
+inlineToMan opts (Link _ txt (src, _)) = do
   linktext <- inlineListToMan opts txt
   let srcSuffix = fromMaybe src (stripPrefix "mailto:" src)
   return $ case txt of
@@ -350,12 +350,12 @@ inlineToMan opts (Link txt (src, _)) = do
              | escapeURI s == srcSuffix ->
                                  char '<' <> text srcSuffix <> char '>'
            _                  -> linktext <> text " (" <> text src <> char ')'
-inlineToMan opts (Image _ alternate (source, tit)) = do
+inlineToMan opts (Image attr alternate (source, tit)) = do
   let txt = if (null alternate) || (alternate == [Str ""]) ||
                (alternate == [Str source]) -- to prevent autolinks
                then [Str "image"]
                else alternate
-  linkPart <- inlineToMan opts (Link txt (source, tit))
+  linkPart <- inlineToMan opts (Link attr txt (source, tit))
   return $ char '[' <> text "IMAGE: " <> linkPart <> char ']'
 inlineToMan _ (Note contents) = do
   -- add to notes in state
diff --git a/src/Text/Pandoc/Writers/Markdown.hs b/src/Text/Pandoc/Writers/Markdown.hs
index f809e5d19..019a0e272 100644
--- a/src/Text/Pandoc/Writers/Markdown.hs
+++ b/src/Text/Pandoc/Writers/Markdown.hs
@@ -55,7 +55,8 @@ import qualified Data.Vector as V
 import qualified Data.Text as T
 
 type Notes = [[Block]]
-type Refs = [([Inline], Target)]
+type Ref   = ([Inline], Target, Attr)
+type Refs  = [Ref]
 data WriterState = WriterState { stNotes           :: Notes
                                , stRefs            :: Refs
                                , stRefShortcutable :: Bool
@@ -200,15 +201,16 @@ refsToMarkdown opts refs = mapM (keyToMarkdown opts) refs >>= return . vcat
 
 -- | Return markdown representation of a reference key.
 keyToMarkdown :: WriterOptions
-              -> ([Inline], (String, String))
+              -> Ref
               -> State WriterState Doc
-keyToMarkdown opts (label, (src, tit)) = do
+keyToMarkdown opts (label, (src, tit), attr) = do
   label' <- inlineListToMarkdown opts label
   let tit' = if null tit
                 then empty
                 else space <> "\"" <> text tit <> "\""
   return $ nest 2 $ hang 2
             ("[" <> label' <> "]:" <> space) (text src <> tit')
+            <> linkAttributes opts attr
 
 -- | Return markdown representation of notes.
 notesToMarkdown :: WriterOptions -> [[Block]] -> State WriterState Doc
@@ -280,6 +282,12 @@ attrsToMarkdown attribs = braces $ hsep [attribId, attribClasses, attribKeys]
                                             map (\(k,v) -> text k
                                               <> "=\"" <> text v <> "\"") ks
 
+linkAttributes :: WriterOptions -> Attr -> Doc
+linkAttributes opts attr =
+  if isEnabled Ext_common_link_attributes opts && attr /= nullAttr
+     then attrsToMarkdown attr
+     else empty
+
 -- | Ordered list start parser for use in Para below.
 olMarker :: Parser [Char] ParserState Char
 olMarker = do (start, style', delim) <- anyOrderedListMarker
@@ -665,21 +673,21 @@ blockListToMarkdown opts blocks =
 
 -- | Get reference for target; if none exists, create unique one and return.
 --   Prefer label if possible; otherwise, generate a unique key.
-getReference :: [Inline] -> Target -> State WriterState [Inline]
-getReference label (src, tit) = do
+getReference :: Attr -> [Inline] -> Target -> State WriterState [Inline]
+getReference attr label target = do
   st <- get
-  case find ((== (src, tit)) . snd) (stRefs st) of
-    Just (ref, _) -> return ref
+  case find (\(_,t,a) -> t == target && a == attr) (stRefs st) of
+    Just (ref, _, _) -> return ref
     Nothing       -> do
-      let label' = case find ((== label) . fst) (stRefs st) of
+      let label' = case find (\(l,_,_) -> l == label) (stRefs st) of
                       Just _ -> -- label is used; generate numerical label
                              case find (\n -> notElem [Str (show n)]
-                                                      (map fst (stRefs st)))
+                                                      (map (\(l,_,_) -> l) (stRefs st)))
                                        [1..(10000 :: Integer)] of
                                   Just x  -> [Str (show x)]
                                   Nothing -> error "no unique label"
                       Nothing -> label
-      modify (\s -> s{ stRefs = (label', (src,tit)) : stRefs st })
+      modify (\s -> s{ stRefs = (label', target, attr) : stRefs st })
       return label'
 
 -- | Convert list of Pandoc inline elements to markdown.
@@ -689,10 +697,10 @@ inlineListToMarkdown opts lst = do
   go (if inlist then avoidBadWrapsInList lst else lst)
   where go [] = return empty
         go (i:is) = case i of
-            (Link _ _) -> case is of
+            (Link _ _ _) -> case is of
                 -- If a link is followed by another link or '[' we don't shortcut
-                (Link _ _):_                  -> unshortcutable
-                Space:(Link _ _):_            -> unshortcutable
+                (Link _ _ _):_                -> unshortcutable
+                Space:(Link _ _ _):_          -> unshortcutable
                 Space:(Str('[':_)):_          -> unshortcutable
                 Space:(RawInline _ ('[':_)):_ -> unshortcutable
                 Space:(Cite _ _):_            -> unshortcutable
@@ -883,7 +891,7 @@ inlineToMarkdown opts (Cite (c:cs) lst)
            return $ pdoc <+> r
         modekey SuppressAuthor = "-"
         modekey _              = ""
-inlineToMarkdown opts (Link txt (src, tit)) = do
+inlineToMarkdown opts (Link attr txt (src, tit)) = do
   plain <- gets stPlain
   linktext <- inlineListToMarkdown opts txt
   let linktitle = if null tit
@@ -898,7 +906,7 @@ inlineToMarkdown opts (Link txt (src, tit)) = do
   shortcutable <- gets stRefShortcutable
   let useShortcutRefLinks = shortcutable &&
                             isEnabled Ext_shortcut_reference_links opts
-  ref <- if useRefLinks then getReference txt (src, tit) else return []
+  ref <- if useRefLinks then getReference attr txt (src, tit) else return []
   reftext <- inlineListToMarkdown opts ref
   return $ if useAuto
               then if plain
@@ -915,21 +923,18 @@ inlineToMarkdown opts (Link txt (src, tit)) = do
                       else if plain
                               then linktext
                               else "[" <> linktext <> "](" <>
-                                   text src <> linktitle <> ")"
+                                   text src <> linktitle <> ")" <>
+                                   linkAttributes opts attr
 inlineToMarkdown opts (Image attr alternate (source, tit)) = do
   plain <- gets stPlain
   let txt = if null alternate || alternate == [Str source]
                                  -- to prevent autolinks
                then [Str ""]
                else alternate
-  linkPart <- inlineToMarkdown opts (Link txt (source, tit))
+  linkPart <- inlineToMarkdown opts (Link attr txt (source, tit))
   return $ if plain
               then "[" <> linkPart <> "]"
-              else "!" <> linkPart <>
-                   if isEnabled Ext_common_link_attributes opts
-                      && attr /= nullAttr
-                      then attrsToMarkdown attr
-                      else empty
+              else "!" <> linkPart
 inlineToMarkdown opts (Note contents) = do
   modify (\st -> st{ stNotes = contents : stNotes st })
   st <- get
diff --git a/src/Text/Pandoc/Writers/MediaWiki.hs b/src/Text/Pandoc/Writers/MediaWiki.hs
index 5c51157ea..1aae15354 100644
--- a/src/Text/Pandoc/Writers/MediaWiki.hs
+++ b/src/Text/Pandoc/Writers/MediaWiki.hs
@@ -399,7 +399,7 @@ inlineToMediaWiki (LineBreak) = return "<br />\n"
 
 inlineToMediaWiki Space = return " "
 
-inlineToMediaWiki (Link txt (src, _)) = do
+inlineToMediaWiki (Link _ txt (src, _)) = do
   label <- inlineListToMediaWiki txt
   case txt of
      [Str s] | isURI src && escapeURI s == src -> return src
diff --git a/src/Text/Pandoc/Writers/OpenDocument.hs b/src/Text/Pandoc/Writers/OpenDocument.hs
index 1935a630f..7b964e2d2 100644
--- a/src/Text/Pandoc/Writers/OpenDocument.hs
+++ b/src/Text/Pandoc/Writers/OpenDocument.hs
@@ -393,7 +393,7 @@ inlineToOpenDocument o ils
     | RawInline f s <- ils = if f == Format "opendocument"
                                 then return $ text s
                                 else return empty
-    | Link  l (s,t) <- ils = mkLink s t <$> inlinesToOpenDocument o l
+    | Link _ l (s,t) <- ils = mkLink s t <$> inlinesToOpenDocument o l
     | Image attr _ (s,t) <- ils = mkImg attr s t
     | Note        l <- ils = mkNote l
     | otherwise            = return empty
diff --git a/src/Text/Pandoc/Writers/Org.hs b/src/Text/Pandoc/Writers/Org.hs
index ffd271810..24da7b9e1 100644
--- a/src/Text/Pandoc/Writers/Org.hs
+++ b/src/Text/Pandoc/Writers/Org.hs
@@ -276,7 +276,7 @@ inlineToOrg (RawInline f str) | f == "tex" || f == "latex" = return $ text str
 inlineToOrg (RawInline _ _) = return empty
 inlineToOrg (LineBreak) = return (text "\\\\" <> cr)
 inlineToOrg Space = return space
-inlineToOrg (Link txt (src, _)) = do
+inlineToOrg (Link _ txt (src, _)) = do
   case txt of
         [Str x] | escapeURI x == src ->  -- autolink
              do modify $ \s -> s{ stLinks = True }
diff --git a/src/Text/Pandoc/Writers/RST.hs b/src/Text/Pandoc/Writers/RST.hs
index 4b68984d0..a65d6f8bb 100644
--- a/src/Text/Pandoc/Writers/RST.hs
+++ b/src/Text/Pandoc/Writers/RST.hs
@@ -390,7 +390,7 @@ inlineListToRST lst =
         isComplex (Strikeout _) = True
         isComplex (Superscript _) = True
         isComplex (Subscript _) = True
-        isComplex (Link _ _) = True
+        isComplex (Link _ _ _) = True
         isComplex (Image _ _ _) = True
         isComplex (Code _ _) = True
         isComplex (Math _ _) = True
@@ -442,17 +442,17 @@ inlineToRST (RawInline f x)
 inlineToRST (LineBreak) = return cr -- there's no line break in RST (see Para)
 inlineToRST Space = return space
 -- autolink
-inlineToRST (Link [Str str] (src, _))
+inlineToRST (Link _ [Str str] (src, _))
   | isURI src &&
     if "mailto:" `isPrefixOf` src
        then src == escapeURI ("mailto:" ++ str)
        else src == escapeURI str = do
   let srcSuffix = fromMaybe src (stripPrefix "mailto:" src)
   return $ text srcSuffix
-inlineToRST (Link [Image attr alt (imgsrc,imgtit)] (src, _tit)) = do
+inlineToRST (Link _ [Image attr alt (imgsrc,imgtit)] (src, _tit)) = do
   label <- registerImage attr alt (imgsrc,imgtit) (Just src)
   return $ "|" <> label <> "|"
-inlineToRST (Link txt (src, tit)) = do
+inlineToRST (Link _ txt (src, tit)) = do
   useReferenceLinks <- get >>= return . writerReferenceLinks . stOptions
   linktext <- inlineListToRST $ normalizeSpaces txt
   if useReferenceLinks
diff --git a/src/Text/Pandoc/Writers/RTF.hs b/src/Text/Pandoc/Writers/RTF.hs
index c89e88fad..dabe5cf78 100644
--- a/src/Text/Pandoc/Writers/RTF.hs
+++ b/src/Text/Pandoc/Writers/RTF.hs
@@ -350,7 +350,7 @@ inlineToRTF (RawInline f str)
   | otherwise         = ""
 inlineToRTF (LineBreak) = "\\line "
 inlineToRTF Space = " "
-inlineToRTF (Link text (src, _)) =
+inlineToRTF (Link _ text (src, _)) =
   "{\\field{\\*\\fldinst{HYPERLINK \"" ++ (codeStringToRTF src) ++
   "\"}}{\\fldrslt{\\ul\n" ++ (inlineListToRTF text) ++ "\n}}}\n"
 inlineToRTF (Image _ _ (source, _)) =
diff --git a/src/Text/Pandoc/Writers/Texinfo.hs b/src/Text/Pandoc/Writers/Texinfo.hs
index a8e1e15a6..cd9e2ef3d 100644
--- a/src/Text/Pandoc/Writers/Texinfo.hs
+++ b/src/Text/Pandoc/Writers/Texinfo.hs
@@ -427,11 +427,11 @@ inlineToTexinfo (RawInline f str)
 inlineToTexinfo (LineBreak) = return $ text "@*" <> cr
 inlineToTexinfo Space = return space
 
-inlineToTexinfo (Link txt (src@('#':_), _)) = do
+inlineToTexinfo (Link _ txt (src@('#':_), _)) = do
   contents <- escapeCommas $ inlineListToTexinfo txt
   return $ text "@ref" <>
            braces (text (stringToTexinfo src) <> text "," <> contents)
-inlineToTexinfo (Link txt (src, _)) = do
+inlineToTexinfo (Link _ txt (src, _)) = do
   case txt of
         [Str x] | escapeURI x == src ->  -- autolink
              do return $ text $ "@url{" ++ x ++ "}"
diff --git a/src/Text/Pandoc/Writers/Textile.hs b/src/Text/Pandoc/Writers/Textile.hs
index dde9a7177..456bf19c9 100644
--- a/src/Text/Pandoc/Writers/Textile.hs
+++ b/src/Text/Pandoc/Writers/Textile.hs
@@ -426,23 +426,25 @@ inlineToTextile _ (LineBreak) = return "\n"
 
 inlineToTextile _ Space = return " "
 
-inlineToTextile opts (Link txt (src, _)) = do
+inlineToTextile opts (Link (_, cls, _) txt (src, _)) = do
+  let classes = if null cls
+                   then ""
+                   else "(" ++ unwords cls ++ ")"
   label <- case txt of
                 [Code _ s]
                  | s == src -> return "$"
                 [Str s]
                  | s == src -> return "$"
                 _           -> inlineListToTextile opts txt
-  return $ "\"" ++ label ++ "\":" ++ src
+  return $ "\"" ++ classes ++ label ++ "\":" ++ src
 
-inlineToTextile opts (Image attr alt (source, tit)) = do
+inlineToTextile opts (Image attr@(_, cls, _) alt (source, tit)) = do
   alt' <- inlineListToTextile opts alt
   let txt = if null tit
                then if null alt'
                        then ""
                        else "(" ++ alt' ++ ")"
                else "(" ++ tit ++ ")"
-      (_, cls, _) = attr
       classes = if null cls
                    then ""
                    else "(" ++ unwords cls ++ ")"