diff --git a/plugins/fn-lookup/functions.c b/plugins/fn-lookup/functions.c
index f1eed50..3f1ca16 100644
--- a/plugins/fn-lookup/functions.c
+++ b/plugins/fn-lookup/functions.c
@@ -1686,10 +1686,15 @@ static GnmFuncHelp const help_hyperlink[] = {
 static GnmValue *
 gnumeric_hyperlink (GnmFuncEvalInfo *ei, GnmValue const * const *args)
 {
-	GnmValue const * v = args[1];
-	if (v == NULL)
-		v = args[0];
-	return value_dup (v);
+	GnmValue const *l = args[0];
+	GnmValue const *v = args[1] ? args[1] : l;
+
+	/*
+	 * If the special GNM_EXPR_EVAL_HYPERLINK flag is set, return the
+	 * link.  Otherwise return the value.
+	 */
+
+	return value_dup ((ei->flags & GNM_EXPR_EVAL_HYPERLINK) ? l : v);
 }
 
 /***************************************************************************/
diff --git a/src/dependent.c b/src/dependent.c
index 718c960..c04f669 100644
--- a/src/dependent.c
+++ b/src/dependent.c
@@ -1627,6 +1627,8 @@ gnm_cell_eval_content (GnmCell *cell)
 	eval_pos_init_cell (&pos, cell);
 	cell->base.flags |= DEPENDENT_BEING_CALCULATED;
 
+	g_hash_table_remove (cell->base.sheet->hyperlinks, cell);
+
 	/*
 	 * I'm somewhat afraid of thinking about the semantics of iteration
 	 * if different workbooks have different settings.
diff --git a/src/func.c b/src/func.c
index 1715f6b..ccc81c0 100644
--- a/src/func.c
+++ b/src/func.c
@@ -1780,7 +1780,8 @@ function_call_with_exprs (GnmFuncEvalInfo *ei)
 		return gnumeric_if2 (ei, argc, argv, flags);
 
 	pass_flags = (flags &
-		      (GNM_EXPR_EVAL_ARRAY_CONTEXT));
+		      (GNM_EXPR_EVAL_ARRAY_CONTEXT |
+		       GNM_EXPR_EVAL_HYPERLINK));
 
 	for (i = 0; i < argc; i++) {
 		char arg_type = fn_def->fn.args.arg_types[i];
diff --git a/src/gnumeric.h b/src/gnumeric.h
index e8bcbbb..0ed549a 100644
--- a/src/gnumeric.h
+++ b/src/gnumeric.h
@@ -85,7 +85,8 @@ typedef enum {
 	GNM_EXPR_EVAL_PERMIT_NON_SCALAR	= 0x1,
 	GNM_EXPR_EVAL_PERMIT_EMPTY	= 0x2,
 	GNM_EXPR_EVAL_WANT_REF		= 0x4,
-	GNM_EXPR_EVAL_ARRAY_CONTEXT     = 0x8
+	GNM_EXPR_EVAL_ARRAY_CONTEXT     = 0x8,
+	GNM_EXPR_EVAL_HYPERLINK		= 0x10
 } GnmExprEvalFlags;
 
 G_END_DECLS
diff --git a/src/hlink.c b/src/hlink.c
index 393f2da..9a8bb3b 100644
--- a/src/hlink.c
+++ b/src/hlink.c
@@ -38,6 +38,8 @@
 #include "expr.h"
 #include "value.h"
 #include "mstyle.h"
+#include "func.h"
+#include "cell.h"
 
 #include <goffice/goffice.h>
 #include <gsf/gsf-impl-utils.h>
@@ -64,18 +66,72 @@ gnm_hlink_activate (GnmHLink *lnk, WBCGtk *wbcg)
 	return GET_CLASS (lnk)->Activate (lnk, wbcg);
 }
 
+/*
+ * Approximation: we want to check for hyperlink calls anywhere.
+ */
+static gboolean
+is_hyperlink_call (const GnmExprTop *texpr)
+{
+	GnmExpr const *fcall = texpr
+		? gnm_expr_top_first_funcall (texpr)
+		: NULL;
+	GnmFunc *func;
+
+	if (!fcall)
+		return FALSE;
+
+	func = gnm_expr_get_func_def (fcall);
+	return strcmp (func->name, "hyperlink") == 0;
+}
+
 /**
  * sheet_hlink_find:
  * @sheet: #Sheet
  * @pos: #GcmCellPos
  *
- * Returns: (transfer none): the found #GnmHLink.
+ * Returns: (transfer none): the found #GnmHLink, if any.
  **/
 GnmHLink *
 sheet_hlink_find (Sheet const *sheet, GnmCellPos const *pos)
 {
-	GnmStyle const *style = sheet_style_get (sheet, pos->col, pos->row);
-	return gnm_style_get_hlink (style);
+	GnmCell *cell = sheet_cell_get (sheet, pos->col, pos->row);
+
+	if (cell && is_hyperlink_call (cell->base.texpr)) {
+		GnmHLink *hlink;
+		const char *target;
+		char *tip;
+		Sheet *sheet = cell->base.sheet;
+		GnmValue *v;
+		GnmEvalPos ep;
+
+		hlink = g_hash_table_lookup (sheet->hyperlinks, cell);
+		if (hlink)
+			return hlink;
+
+		hlink = g_object_new (gnm_hlink_url_get_type (), NULL);
+		g_hash_table_insert (sheet->hyperlinks, cell, hlink);
+
+		eval_pos_init_cell (&ep, cell);
+		v = gnm_expr_top_eval (cell->base.texpr, &ep,
+				       GNM_EXPR_EVAL_HYPERLINK |
+				       GNM_EXPR_EVAL_PERMIT_EMPTY);
+		target = value_peek_string (v);
+		tip = g_strdup_printf (_("Go to %s..."), target);
+
+		gnm_hlink_set_target (hlink, target);
+		/* xgettext: tooltip for HYPERLINK.  */
+		gnm_hlink_set_tip (hlink, tip);
+		g_free (tip);
+
+		value_release (v);
+
+		return hlink;
+	} else {
+		GnmStyle const *style =
+			sheet_style_get (sheet, pos->col, pos->row);
+		GnmHLink *hlink = gnm_style_get_hlink (style);
+		return hlink;
+	}
 }
 
 static void
diff --git a/src/item-grid.c b/src/item-grid.c
index 5254c81..0d94aad 100644
--- a/src/item-grid.c
+++ b/src/item-grid.c
@@ -72,7 +72,7 @@ struct _GnmItemGrid {
 	/* information for the cursor motion handler */
 	guint cursor_timer;
 	gint64 last_x, last_y;
-	GnmHLink *cur_link; /* do not dereference, just a pointer */
+	GnmHLink *cur_link;
 	GtkWidget *tip;
 	guint tip_timer;
 
@@ -156,9 +156,9 @@ item_grid_finalize (GObject *object)
 		ig->cursor_timer = 0;
 	}
 	ig_clear_hlink_tip (ig);
-	ig->cur_link = NULL;
+	g_clear_object (&ig->cur_link);
 
-	(*G_OBJECT_CLASS (parent_class)->finalize) (object);
+	G_OBJECT_CLASS (parent_class)->finalize (object);
 }
 
 static gint
@@ -176,7 +176,13 @@ cb_cursor_motion (GnmItemGrid *ig)
 
 	old_link = ig->cur_link;
 	ig->cur_link = sheet_hlink_find (sheet, &pos);
-	cursor = (ig->cur_link != NULL) ? ig->cursor_link : ig->cursor_cross;
+	if (ig->cur_link) {
+		g_object_ref (ig->cur_link);
+		cursor = ig->cursor_link;
+	} else {
+		cursor = ig->cursor_cross;
+	}
+
 	if (pane->mouse_cursor != cursor) {
 		gnm_pane_mouse_cursor_set (pane, cursor);
 		scg_set_display_cursor (ig->scg);
@@ -191,6 +197,10 @@ cb_cursor_motion (GnmItemGrid *ig)
 		gtk_widget_destroy (gtk_widget_get_toplevel (ig->tip));
 		ig->tip = NULL;
 	}
+
+	if (old_link)
+		g_object_unref (old_link);
+
 	return FALSE;
 }
 
@@ -1050,36 +1060,22 @@ cb_extend_expr_range (GnmPane *pane, GnmPaneSlideInfo const *info)
 static gint
 cb_cursor_come_to_rest (GnmItemGrid *ig)
 {
-	Sheet const *sheet = scg_sheet (ig->scg);
 	GocCanvas *canvas = ig->canvas_item.canvas;
-	GnmPane *pane = GNM_PANE (canvas);
-	GnmHLink *link;
-	gint64 x, y;
-	GnmCellPos pos;
 	char const *tiptext;
 
-	/* Be anal and look it up in case something has destroyed the link
-	 * since the last motion */
-	x = ig->last_x;
-	y = ig->last_y;
-	pos.col = gnm_pane_find_col (pane, x, NULL);
-	pos.row = gnm_pane_find_row (pane, y, NULL);
-
-	link = sheet_hlink_find (sheet, &pos);
-	if (link != NULL && (tiptext = gnm_hlink_get_tip (link)) != NULL) {
-		g_return_val_if_fail (link == ig->cur_link, FALSE);
-
-		if (ig->tip == NULL && strlen (tiptext) > 0) {
-			GtkWidget *cw = GTK_WIDGET (canvas);
-			int wx, wy;
-
-			gnm_canvas_get_position (canvas, &wx, &wy,
-						 ig->last_x, ig->last_y);
-			ig->tip = gnumeric_create_tooltip (cw);
-			gtk_label_set_text (GTK_LABEL (ig->tip), tiptext);
-			gnumeric_position_tooltip (ig->tip, wx, wy, TRUE);
-			gtk_widget_show_all (gtk_widget_get_toplevel (ig->tip));
-		}
+	if (ig->tip == NULL &&
+	    ig->cur_link &&
+	    ((tiptext = gnm_hlink_get_tip (ig->cur_link))) &&
+	    *tiptext != 0) {
+		GtkWidget *cw = GTK_WIDGET (canvas);
+		int wx, wy;
+
+		gnm_canvas_get_position (canvas, &wx, &wy,
+					 ig->last_x, ig->last_y);
+		ig->tip = gnumeric_create_tooltip (cw);
+		gtk_label_set_text (GTK_LABEL (ig->tip), tiptext);
+		gnumeric_position_tooltip (ig->tip, wx, wy, TRUE);
+		gtk_widget_show_all (gtk_widget_get_toplevel (ig->tip));
 	}
 
 	return FALSE;
diff --git a/src/sheet.c b/src/sheet.c
index d1091a0..6beeb77 100644
--- a/src/sheet.c
+++ b/src/sheet.c
@@ -817,6 +817,12 @@ gnm_sheet_init (Sheet *sheet)
 	sheet->cell_hash = g_hash_table_new ((GHashFunc)&cell_set_hash,
 					     (GCompareFunc)&cell_set_equal);
 
+	sheet->hyperlinks = g_hash_table_new_full
+		((GHashFunc)&cell_set_hash,
+		 (GCompareFunc)&cell_set_equal,
+		 NULL,
+		 g_object_unref);
+
 	/* Init preferences */
 	sheet->convs = gnm_conventions_default;
 
@@ -4270,6 +4276,7 @@ sheet_cell_remove_from_hash (Sheet *sheet, GnmCell *cell)
 	if (gnm_cell_expr_is_linked (cell))
 		dependent_unlink (GNM_CELL_TO_DEP (cell));
 	g_hash_table_remove (sheet->cell_hash, cell);
+	g_hash_table_remove (sheet->hyperlinks, cell);
 	cell->base.flags &= ~(GNM_CELL_IN_SHEET_LIST|GNM_CELL_IS_MERGED);
 }
 
@@ -4478,6 +4485,10 @@ sheet_destroy_contents (Sheet *sheet)
 	for (i = sheet->rows.max_used; i >= 0 ; --i)
 		row_destroy_span (sheet_row_get (sheet, i));
 
+	/* Remove all cells from hyperlinks keys while cells stille exist.  */
+	g_hash_table_destroy (sheet->hyperlinks);
+	sheet->hyperlinks = NULL;
+
 	/* Remove all the cells */
 	sheet_cell_foreach (sheet, (GHFunc) &cb_remove_allcells, NULL);
 	g_hash_table_destroy (sheet->cell_hash);
diff --git a/src/sheet.h b/src/sheet.h
index 6d9a565..dbf6c95 100644
--- a/src/sheet.h
+++ b/src/sheet.h
@@ -48,6 +48,8 @@ struct _Sheet {
 
 	GHashTable  *cell_hash;	/* The cells in hashed format */
 
+	GHashTable *hyperlinks;	/* Cell->GnmHLink for computed links */
+
 	GnmNamedExprCollection *names;
 
 	/* This should eventually be moved to the views.  */
